beginning of Capaxitor, simple lead sampler

master
zetaPRIME 2021-11-09 19:56:01 -05:00
parent 82bb4e48e1
commit 26a2bf4e82
5 changed files with 211 additions and 1 deletions

7
notes
View File

@ -32,6 +32,13 @@ parameters {
TODO {
immediate frontburner {
PLAYBACK BREAKS AFTER FIRST PLAY??? {
doesn't happen in Lovely Storm; only where Capaxitor is used??
resolves itself after project switch
does NOT break preview mode??
seems to be only with the Maybe sample??? idk
}
distortion effect
single-selection sampler

View File

@ -1,10 +1,29 @@
#pragma once
#include <cmath>
#include <cstddef>
#include <array>
#include "data/audioframe.h"
#include "data/sample.h"
namespace Xybrid::NodeLib {
const constexpr size_t LUT_TAPS = 8;
const constexpr size_t LUT_STEPS = 1024;
extern const std::array<std::array<double, LUT_TAPS>, LUT_STEPS> resamplerLUT;
inline Data::AudioFrame resamp(Data::Sample* smp, double pos) {
double ip = std::floor(pos);
auto& pt = NodeLib::resamplerLUT[static_cast<size_t>(pos - ip*NodeLib::LUT_STEPS) % NodeLib::LUT_STEPS];
Data::AudioFrame out(0.0);
auto ii = static_cast<ptrdiff_t>(ip) - 3;
for (size_t i = 0; i < 8; i++) {
auto si = ii+static_cast<ptrdiff_t>(i);
if (si >= 0 && si < static_cast<ptrdiff_t>(smp->length())) out += (*smp)[static_cast<size_t>(si)] * pt[i];
}
return out;
}
}

View File

@ -131,7 +131,7 @@ void BeatPad::process() { core.process(this); }
void BeatPad::saveData(QCborMap& m) const {
QCborMap cm;
for (auto c : cfg) {
for (auto& c : cfg) {
if (auto smp = c.second->smp.lock(); smp) {
QCborMap e;
e[qs("sample")] = QCborValue(smp->uuid);

View File

@ -0,0 +1,137 @@
#include "capaxitor.h"
using Xybrid::Instruments::Capaxitor;
using namespace Xybrid::NodeLib;
using Note = InstrumentCore::Note;
using namespace Xybrid::Data;
#include "nodelib/commandreader.h"
#include "nodelib/resampler.h"
#include "data/project.h"
#include "data/sample.h"
#include "data/porttypes.h"
#include "config/pluginregistry.h"
using namespace Xybrid::Config;
#include "audio/audioengine.h"
using namespace Xybrid::Audio;
#include "ui/waveformpreviewwidget.h"
#include "ui/patchboard/nodeobject.h"
#include "ui/gadgets/layoutgadget.h"
#include "ui/gadgets/knobgadget.h"
#include "ui/gadgets/selectorgadget.h"
#include "ui/gadgets/sampleselectorgadget.h"
using namespace Xybrid::UI;
#include "ui/patchboard/nodeuiscene.h"
#include "uisocket.h"
#include "util/strings.h"
#include <cmath>
#include <QDebug>
#include <QCborMap>
#include <QCborValue>
#include <QCborArray>
#include <QMenu>
#include <QGraphicsProxyWidget>
#define qs QStringLiteral
namespace {
bool _ = PluginRegistry::enqueueRegistration([] {
auto i = std::make_shared<PluginInfo>();
i->id = "plug:capaxitor";
i->displayName = "Capaxitor";
i->category = "Sampler";
i->createInstance = []{ return std::make_shared<Capaxitor>(); };
PluginRegistry::registerPlugin(i);
//inf = i;
});
}
Capaxitor::Capaxitor() {
}
void Capaxitor::init() {
addPort(Port::Input, Port::Command, 0);
addPort(Port::Output, Port::Audio, 0);
core.onNoteOn = [](Note& note) {
auto& data = *reinterpret_cast<NoteData*>(&note.scratch);
new (&data) NoteData(); // construct in-place
note.adsr = {0.0, 0.0, 1.0, shortStep}; // minimal tweening
};
core.onDeleteNote = [](Note& note) {
auto& data = *reinterpret_cast<NoteData*>(&note.scratch);
data.~NoteData(); // destroy
};
/*core.globalParam['Q'] = [](const ParamReader& pr) {
qDebug() << "global recieved" << pr.param() << pr.val();
return true;
};*/
core.processNote = [this](Note& note, AudioPort* p) {
auto& data = *reinterpret_cast<NoteData*>(&note.scratch);
auto smp = this->smp.lock();
if (!smp) return core.deleteNote(note);
double rate = static_cast<double>(smp->sampleRate) / static_cast<double>(audioEngine->curSampleRate());
size_t ts = p->size;
for (size_t i = 0; i < ts; i++) {
core.advanceNote(note);
double n = note.effectiveNote();
double fr = std::pow(SEMI, n - 12*5);
// actual sample pos
double sp = data.sampleTime * rate;
auto out = NodeLib::resamp(smp.get(), sp);
(*p)[i] += out.gainBalance(0, note.pan) * note.ampMult();
data.sampleTime += fr;
}
};
}
void Capaxitor::reset() { core.reset(); }
void Capaxitor::release() { core.release(); }
void Capaxitor::process() { core.process(this); }
void Capaxitor::saveData(QCborMap& m) const {
if (auto smp = this->smp.lock(); smp) {
m[qs("sample")] = QCborValue(smp->uuid);
smp->markForExport();
}
}
void Capaxitor::loadData(const QCborMap& m) {
auto id = m.value("sample").toUuid();
if (auto f = project->samples.find(id); f != project->samples.end()) {
smp = f.value();
//c->start = m.value("start").toInteger(-1);
//c->end = m.value("end").toInteger(-1);
}
}
void Capaxitor::onGadgetCreated() {
if (!obj) return;
auto l = new LayoutGadget(obj);
auto sampleSelector = new SampleSelectorGadget(project, l);
sampleSelector->setSample(smp.lock(), false);
QObject::connect(sampleSelector, &SampleSelectorGadget::sampleSelected, [=](auto smp) { this->smp = smp; });
}
void Capaxitor::onDoubleClick() { emit project->socket->openNodeUI(this); }

View File

@ -0,0 +1,47 @@
#pragma once
#include "nodelib/instrumentcore.h"
namespace Xybrid::Data { class Sample; }
namespace Xybrid::Instruments {
class Capaxitor : public Data::Node {
NodeLib::InstrumentCore core;
struct NoteData {
double sampleTime = 0;
NoteData() = default;
~NoteData() = default;
};
static_assert (sizeof(NoteData) <= sizeof(NodeLib::InstrumentCore::Note::scratch), "Note data overflows scratch space!");
std::weak_ptr<Data::Sample> smp = std::weak_ptr<Data::Sample>();
public:
Capaxitor();
~Capaxitor() override = default;
void init() override;
void reset() override;
void release() override;
void process() override;
//void onRename() override;
void saveData(QCborMap&) const override;
void loadData(const QCborMap&) override;
//void onUnparent(std::shared_ptr<Data::Graph>) override;
//void onParent(std::shared_ptr<Data::Graph>) override;
void onGadgetCreated() override;
//void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override;
void onDoubleClick() override;
//void initUI(UI::NodeUIScene*) override;
};
}