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
#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
{
@ -10,6 +32,7 @@ class Scale
// freqs in hz, periods in samples
std::vector<double> frequencies;
std::vector<double> periods;
std::vector<std::string> names;
public:
void newSampleRate(double rate)
@ -23,16 +46,18 @@ public:
// Default ctor: 12TET @ 48kHz
Scale(double hz = 440.0)
{
hz /= 32.0;
hz /= 64.0;
for (int i = 0; i < 12 * 8; ++i)
{
frequencies.push_back(hz);
periods.push_back(sampleRate / hz);
names.push_back(defaultNoteNames[i % 12]);
hz *= exp2(1.0 / 12.0);
}
}
double getNearestPeriod(double period)
{
for (auto note : periods)
@ -45,6 +70,26 @@ public:
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
};

View File

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

View File

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

View File

@ -18,7 +18,7 @@ protected:
const char* getLabel() const override { return "yaw-shepard"; }
const char* getDescription() const override { return "Generalized Shepard tone, tablet UI"; }
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"; }
uint32_t getVersion() const override { return d_version(1, 0, 0); }
int64_t getUniqueId() const override { return d_cconst('y', 's', 'p', 'd'); }
@ -33,6 +33,13 @@ protected:
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:
parameter.name = "x";
parameter.symbol = "x";
@ -105,6 +112,7 @@ protected:
synth.process(*outputs, frames);
parity = !parity;
fParameters[kParameterTime] += parity ? 1 : -1;
fParameters[kFundamentalFrequency] = synth.hzFund;
}
private:

View File

@ -57,7 +57,7 @@ void Synth::process(float* output, const uint32_t frames)
for(uint32_t i = 0; i < frames; i++){
//Set pitch.
hz = hzFund = spectrumPhase * fMin + fMin;
hz = hzFund = exp2(spectrumPhase) * fMin;
bool isOddHarmonic = true;
@ -80,7 +80,7 @@ void Synth::process(float* output, const uint32_t frames)
isOddHarmonic = !isOddHarmonic;
}
output[i] *= volume / static_cast<double>(NUM_VOICES);
output[i] *= 32 * volume / static_cast<double>(NUM_VOICES);
//Wrapping.
spectrumPhase += spectrumPhaseVelocity * sampleInterval;
@ -102,8 +102,10 @@ 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 110.0 Hz (A3)
void Synth::setTimbre(double in){
fMin = exp2(in * 2.0) * 110.0;
}
void Synth::setVolume(double in){

View File

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

View File

@ -1,6 +1,6 @@
#include "ui.h"
#ifdef DEBUG
#include <format>
#ifdef DEBUG
#include <optional>
#endif
@ -113,6 +113,8 @@ void TabUI::parameterChanged(uint32_t index, float value)
{
switch (index)
{
case (kFundamentalFrequency):
hz = value;
case (ktpax):
pkt.x = value;
case (ktpay):
@ -125,8 +127,6 @@ void TabUI::parameterChanged(uint32_t index, float value)
return;
}
getTabletData();
repaint();
}
void TabUI::uiIdle()
@ -173,6 +173,16 @@ void TabUI::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: {}\n",
hz, scale.getNearestNoteNumber(hz))
.c_str(),
nullptr);
closePath();
// Report tablet errors.
#ifdef DEBUG
if (!tab.initialized)

View File

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