From 645ce2e0923aca51d71139c347e4e0d67d13530b Mon Sep 17 00:00:00 2001 From: yaw-man Date: Sun, 18 Sep 2022 13:50:35 -0400 Subject: [PATCH] Harmonic Shepard tone, y axis controls pitch. --- lib/scale/scale.h | 47 ++++++++++++++++++++++++++++- src/yaw-shepard/CMakeLists.txt | 3 +- src/yaw-shepard/DistrhoPluginInfo.h | 3 +- src/yaw-shepard/dsp.cpp | 10 +++++- src/yaw-shepard/synth.cpp | 8 +++-- src/yaw-shepard/synth.h | 10 +++--- src/yaw-shepard/ui.cpp | 16 ++++++++-- src/yaw-shepard/ui.h | 5 +++ 8 files changed, 88 insertions(+), 14 deletions(-) diff --git a/lib/scale/scale.h b/lib/scale/scale.h index 6b46cdf..329b2e4 100644 --- a/lib/scale/scale.h +++ b/lib/scale/scale.h @@ -2,6 +2,28 @@ #define YAW_SCALE_INCLUDED #include +#include + +struct Note +{ + std::string name; + double hz; + unsigned int number; +}; + +static std::vector 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 frequencies; std::vector periods; + std::vector 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 }; diff --git a/src/yaw-shepard/CMakeLists.txt b/src/yaw-shepard/CMakeLists.txt index bb86b7f..3e23d80 100644 --- a/src/yaw-shepard/CMakeLists.txt +++ b/src/yaw-shepard/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/src/yaw-shepard/DistrhoPluginInfo.h b/src/yaw-shepard/DistrhoPluginInfo.h index 13d8023..9d51b45 100644 --- a/src/yaw-shepard/DistrhoPluginInfo.h +++ b/src/yaw-shepard/DistrhoPluginInfo.h @@ -19,7 +19,8 @@ enum Parameters { kParameterButtonA, kParameterButtonB, kParameterTime, - kParameterCount + kFundamentalFrequency, + kParameterCount, }; #endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/src/yaw-shepard/dsp.cpp b/src/yaw-shepard/dsp.cpp index e03493a..ded043e 100644 --- a/src/yaw-shepard/dsp.cpp +++ b/src/yaw-shepard/dsp.cpp @@ -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: diff --git a/src/yaw-shepard/synth.cpp b/src/yaw-shepard/synth.cpp index 2235572..4ef87ac 100644 --- a/src/yaw-shepard/synth.cpp +++ b/src/yaw-shepard/synth.cpp @@ -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(NUM_VOICES); + output[i] *= 32 * volume / static_cast(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){ diff --git a/src/yaw-shepard/synth.h b/src/yaw-shepard/synth.h index a44d6ef..9c2131a 100644 --- a/src/yaw-shepard/synth.h +++ b/src/yaw-shepard/synth.h @@ -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; }; \ No newline at end of file diff --git a/src/yaw-shepard/ui.cpp b/src/yaw-shepard/ui.cpp index ee84d3d..45160c9 100644 --- a/src/yaw-shepard/ui.cpp +++ b/src/yaw-shepard/ui.cpp @@ -1,6 +1,6 @@ #include "ui.h" -#ifdef DEBUG #include +#ifdef DEBUG #include #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) diff --git a/src/yaw-shepard/ui.h b/src/yaw-shepard/ui.h index f532fb1..7f6bb48 100644 --- a/src/yaw-shepard/ui.h +++ b/src/yaw-shepard/ui.h @@ -1,5 +1,6 @@ #include "DistrhoUI.hpp" #include "tablet.h" +#include "scale.h" #ifdef DEBUG #include #include @@ -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) };