Browse Source

Add WAV loading/saving to WTLFO. Add FrequencyQuantity to WTLFO param. Fix WTLFO display bugs.

tags/v2.0.1
Andrew Belt 3 years ago
parent
commit
2885ffb7fe
4 changed files with 8099 additions and 16 deletions
  1. +1
    -0
      Makefile
  2. +153
    -16
      src/WTLFO.cpp
  3. +6659
    -0
      src/dr_wav.c
  4. +1286
    -0
      src/dr_wav.h

+ 1
- 0
Makefile View File

@@ -2,6 +2,7 @@ RACK_DIR ?= ../..


FLAGS += -Idep/include FLAGS += -Idep/include
SOURCES += $(wildcard src/*.cpp) SOURCES += $(wildcard src/*.cpp)
SOURCES += $(wildcard src/*.c)
DISTRIBUTABLES += res DISTRIBUTABLES += res
DISTRIBUTABLES += $(wildcard LICENSE*) DISTRIBUTABLES += $(wildcard LICENSE*)
DISTRIBUTABLES += $(wildcard presets) DISTRIBUTABLES += $(wildcard presets)


+ 153
- 16
src/WTLFO.cpp View File

@@ -1,5 +1,9 @@
#include "plugin.hpp" #include "plugin.hpp"
#include <osdialog.h> #include <osdialog.h>
#include "dr_wav.h"


static const char WAVETABLE_FILTERS[] = "WAV (.wav):wav,WAV";




using simd::float_4; using simd::float_4;
@@ -45,17 +49,37 @@ struct WTLFO : Module {


float_4 phases[4] = {}; float_4 phases[4] = {};
float lastPos = 0.f; float lastPos = 0.f;
float clockFreq = 1.f;
dsp::Timer clockTimer;
bool clockEnabled = false;


dsp::ClockDivider lightDivider; dsp::ClockDivider lightDivider;
dsp::BooleanTrigger offsetTrigger; dsp::BooleanTrigger offsetTrigger;
dsp::BooleanTrigger invertTrigger; dsp::BooleanTrigger invertTrigger;
dsp::SchmittTrigger clockTrigger;
dsp::TSchmittTrigger<float_4> resetTriggers[4];


WTLFO() { WTLFO() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
// TODO Change to momentary with backward compatibility in fromJson() // TODO Change to momentary with backward compatibility in fromJson()
configButton(OFFSET_PARAM, "Offset 0-10V"); configButton(OFFSET_PARAM, "Offset 0-10V");
configButton(INVERT_PARAM, "Invert wave"); configButton(INVERT_PARAM, "Invert wave");
configParam(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1);

struct FrequencyQuantity : ParamQuantity {
float getDisplayValue() override {
WTLFO* module = reinterpret_cast<WTLFO*>(this->module);
if (module->clockFreq == 2.f) {
unit = " Hz";
displayMultiplier = 1.f;
}
else {
unit = "x";
displayMultiplier = 1 / 2.f;
}
return ParamQuantity::getDisplayValue();
}
};
configParam<FrequencyQuantity>(FREQ_PARAM, -8.f, 10.f, 1.f, "Frequency", " Hz", 2, 1);
configParam(POS_PARAM, 0.f, 1.f, 0.f, "Wavetable position", "%", 0.f, 100.f); configParam(POS_PARAM, 0.f, 1.f, 0.f, "Wavetable position", "%", 0.f, 100.f);
configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f); configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
configParam(POS_CV_PARAM, -1.f, 1.f, 0.f, "Wavetable position CV", "%", 0.f, 100.f); configParam(POS_CV_PARAM, -1.f, 1.f, 0.f, "Wavetable position CV", "%", 0.f, 100.f);
@@ -129,16 +153,38 @@ struct WTLFO : Module {
return; return;
} }


// Clock
if (inputs[CLOCK_INPUT].isConnected()) {
clockTimer.process(args.sampleTime);

if (clockTrigger.process(rescale(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
float clockFreq = 1.f / clockTimer.getTime();
clockTimer.reset();
if (0.001f <= clockFreq && clockFreq <= 1000.f) {
this->clockFreq = clockFreq;
}
}
}
else {
// Default frequency when clock is unpatched
clockFreq = 2.f;
}

// Iterate channels
for (int c = 0; c < channels; c += 4) { for (int c = 0; c < channels; c += 4) {
// Calculate frequency in Hz // Calculate frequency in Hz
float_4 pitch = freqParam + inputs[FM_INPUT].getVoltageSimd<float_4>(c) * fmParam; float_4 pitch = freqParam + inputs[FM_INPUT].getVoltageSimd<float_4>(c) * fmParam;
float_4 freq = simd::pow(2.f, pitch);
float_4 freq = clockFreq / 2.f * simd::pow(2.f, pitch);
freq = simd::fmin(freq, 1024.f);


// Accumulate phase // Accumulate phase
float_4 phase = phases[c / 4]; float_4 phase = phases[c / 4];
phase += freq * args.sampleTime; phase += freq * args.sampleTime;
// Wrap phase // Wrap phase
phase -= simd::trunc(phase); phase -= simd::trunc(phase);
// Reset phase
float_4 reset = resetTriggers[c / 4].process(simd::rescale(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c), 0.1f, 2.f, 0.f, 1.f));
phase = simd::ifelse(reset, 0.f, phase);
phases[c / 4] = phase; phases[c / 4] = phase;
// Scale phase from 0 to waveLen // Scale phase from 0 to waveLen
phase *= waveLen; phase *= waveLen;
@@ -197,15 +243,29 @@ struct WTLFO : Module {
lights[PHASE_LIGHT + 1].setBrightness(0.f); lights[PHASE_LIGHT + 1].setBrightness(0.f);
lights[PHASE_LIGHT + 2].setBrightness(1.f); lights[PHASE_LIGHT + 2].setBrightness(1.f);
} }
lights[OFFSET_LIGHT].setBrightness(offset);
lights[INVERT_LIGHT].setBrightness(invert);
} }
} }


void loadWavetable(std::string path) { void loadWavetable(std::string path) {
// TODO
drwav wav;
// TODO Unicode on Windows
if (!drwav_init_file(&wav, path.c_str(), NULL))
return;

waveLen = 0;
wavetable.clear();
wavetable.resize(wav.totalPCMFrameCount * wav.channels);
waveLen = 256;
filename = system::getFilename(path);

drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, wavetable.data());

drwav_uninit(&wav);
} }


void loadWavetableDialog() { void loadWavetableDialog() {
static const char WAVETABLE_FILTERS[] = "WAV (.wav):wav";
osdialog_filters* filters = osdialog_filters_parse(WAVETABLE_FILTERS); osdialog_filters* filters = osdialog_filters_parse(WAVETABLE_FILTERS);
DEFER({osdialog_filters_free(filters);}); DEFER({osdialog_filters_free(filters);});


@@ -219,17 +279,78 @@ struct WTLFO : Module {


loadWavetable(path); loadWavetable(path);
} }

void saveWavetable(std::string path) {
drwav_data_format format;
format.container = drwav_container_riff;
format.format = DR_WAVE_FORMAT_PCM;
format.channels = 1;
format.sampleRate = 44100;
format.bitsPerSample = 16;

drwav wav;
if (!drwav_init_file_write(&wav, path.c_str(), &format, NULL))
return;

size_t len = wavetable.size();
int16_t* buf = new int16_t[len];
drwav_f32_to_s16(buf, wavetable.data(), len);
drwav_write_pcm_frames(&wav, len, buf);
delete[] buf;

drwav_uninit(&wav);
}

void saveWavetableDialog() {
osdialog_filters* filters = osdialog_filters_parse(WAVETABLE_FILTERS);
DEFER({osdialog_filters_free(filters);});

char* pathC = osdialog_file(OSDIALOG_SAVE, NULL, filename.c_str(), filters);
if (!pathC) {
// Cancel silently
return;
}
DEFER({std::free(pathC);});

// Automatically append .wav extension
std::string path = pathC;
if (system::getExtension(path) != ".wav") {
path += ".wav";
}

saveWavetable(path);
}
}; };




static std::vector<float> sineWavetable;

static void sineWavetableInit() {
sineWavetable.clear();
size_t len = 128;
sineWavetable.resize(len);
for (size_t i = 0; i < len; i++) {
float p = float(i) / len;
sineWavetable[i] = std::sin(2 * float(M_PI) * p);
}
}


template <class TModule> template <class TModule>
struct WTDisplay : LedDisplay { struct WTDisplay : LedDisplay {
TModule* module; TModule* module;


void drawLayer(const DrawArgs& args, int layer) override { void drawLayer(const DrawArgs& args, int layer) override {
if (layer == 1) { if (layer == 1) {
if (!module)
return;
// Lazily initialize default wavetable for display
if (sineWavetable.empty())
sineWavetableInit();

// Get module data or defaults
const std::vector<float>& wavetable = module ? module->wavetable : sineWavetable;
size_t waveLen = module ? module->waveLen : sineWavetable.size();
float lastPos = module ? module->lastPos : 0.f;
std::string filename = module ? module->filename : "Basic.wav";


// Draw filename text // Draw filename text
std::shared_ptr<Font> font = APP->window->loadFont(asset::system("res/fonts/ShareTechMono-Regular.ttf")); std::shared_ptr<Font> font = APP->window->loadFont(asset::system("res/fonts/ShareTechMono-Regular.ttf"));
@@ -237,18 +358,19 @@ struct WTDisplay : LedDisplay {
nvgFontFaceId(args.vg, font->handle); nvgFontFaceId(args.vg, font->handle);
nvgTextLetterSpacing(args.vg, -2); nvgTextLetterSpacing(args.vg, -2);
nvgFillColor(args.vg, SCHEME_YELLOW); nvgFillColor(args.vg, SCHEME_YELLOW);
nvgText(args.vg, 4.0, 13.0, module->filename.c_str(), NULL);
nvgText(args.vg, 4.0, 13.0, filename.c_str(), NULL);


// Get wavetable metadata // Get wavetable metadata
size_t waveLen = module->waveLen;
if (waveLen < 2) if (waveLen < 2)
return; return;


size_t wavetableLen = module->wavetable.size() / waveLen;
if (module->lastPos > wavetableLen - 1)
size_t wavetableLen = wavetable.size() / waveLen;
if (wavetableLen < 1)
return; return;
float posF = module->lastPos - std::trunc(module->lastPos);
size_t pos0 = std::trunc(module->lastPos);
if (lastPos > wavetableLen - 1)
return;
float posF = lastPos - std::trunc(lastPos);
size_t pos0 = std::trunc(lastPos);


// Draw scope // Draw scope
nvgScissor(args.vg, RECT_ARGS(args.clipBox)); nvgScissor(args.vg, RECT_ARGS(args.clipBox));
@@ -256,13 +378,14 @@ struct WTDisplay : LedDisplay {
Vec scopePos = Vec(0.0, 13.0); Vec scopePos = Vec(0.0, 13.0);
Rect scopeRect = Rect(scopePos, box.size - scopePos); Rect scopeRect = Rect(scopePos, box.size - scopePos);
scopeRect = scopeRect.shrink(Vec(4, 5)); scopeRect = scopeRect.shrink(Vec(4, 5));
size_t iSkip = waveLen / 128 + 1;


for (size_t i = 0; i <= waveLen; i++) {
for (size_t i = 0; i <= waveLen; i += iSkip) {
// Get wave value // Get wave value
float wave; float wave;
float wave0 = module->wavetable[(i % waveLen) + waveLen * pos0];
float wave0 = wavetable[(i % waveLen) + waveLen * pos0];
if (posF > 0.f) { if (posF > 0.f) {
float wave1 = module->wavetable[(i % waveLen) + waveLen * (pos0 + 1)];
float wave1 = wavetable[(i % waveLen) + waveLen * (pos0 + 1)];
wave = crossfade(wave0, wave1, posF); wave = crossfade(wave0, wave1, posF);
} }
else { else {
@@ -349,9 +472,23 @@ struct WTLFOWidget : ModuleWidget {


menu->addChild(new MenuSeparator); menu->addChild(new MenuSeparator);


menu->addChild(createMenuItem("Load wavetable...", "",
menu->addChild(createMenuItem("Load wavetable", "",
[=]() {module->loadWavetableDialog();} [=]() {module->loadWavetableDialog();}
)); ));

menu->addChild(createMenuItem("Save wavetable", "",
[=]() {module->saveWavetableDialog();}
));

int sizeOffset = 4;
std::vector<std::string> sizeLabels;
for (int i = sizeOffset; i <= 14; i++) {
sizeLabels.push_back(string::f("%d", 1 << i));
}
menu->addChild(createIndexSubmenuItem("Wave points", sizeLabels,
[=]() {return math::log2(module->waveLen) - sizeOffset;},
[=](int i) {module->waveLen = 1 << (i + sizeOffset);}
));
} }
}; };




+ 6659
- 0
src/dr_wav.c
File diff suppressed because it is too large
View File


+ 1286
- 0
src/dr_wav.h
File diff suppressed because it is too large
View File


Loading…
Cancel
Save