beginnings of BeatPad

portability/boost
zetaPRIME 2019-06-21 15:52:31 -04:00
parent d2a39a9be6
commit 36e5705377
9 changed files with 211 additions and 6 deletions

View File

@ -27,7 +27,7 @@ namespace {
QHash<QString, std::shared_ptr<PluginInfo>> plugins;
QString priorityCategories[] {
"Gadget", "Instrument", "Effect"
"Gadget", "Instrument", "Sampler", "Effect"
};
}

View File

@ -17,7 +17,12 @@ namespace Xybrid::Data {
port->bufR[at] = static_cast<float>(f.r);
return *this;
}
operator AudioFrame() const { return {port->bufL[at], port->bufR[at]}; }
FrameRef& operator+=(AudioFrame f) {
port->bufL[at] += static_cast<float>(f.l);
port->bufR[at] += static_cast<float>(f.r);
return *this;
}
operator AudioFrame() const { return { port->bufL[at], port->bufR[at] }; }
AudioFrame operator*(AudioFrame o) const { return static_cast<AudioFrame>(*this) * o; }
};
@ -28,7 +33,8 @@ namespace Xybrid::Data {
AudioPort() = default;
~AudioPort() override = default;
FrameRef operator[](size_t at) { return {this, at}; }
FrameRef operator[](size_t at) { return { this, at }; }
FrameRef at(size_t at) { return { this, at }; } // alternative syntax if you don't like (*p)[0]
Port::DataType dataType() const override { return Port::Audio; }

View File

@ -22,6 +22,7 @@ namespace Xybrid::NodeLib {
ADSR normalized();
ADSR() = default;
inline ADSR(double a, double d, double s, double r) : a(a), d(d), s(s), r(r) { }
ADSR(const QCborMap&);
ADSR(const QCborValue&);

View File

@ -18,10 +18,19 @@ Note::Note(InstrumentCore* core, uint16_t id) {
this->id = id;
}
void InstrumentCore::reset() {
InstrumentCore::~InstrumentCore() {
release();
}
void InstrumentCore::release() {
if (onDeleteNote) for (auto n : activeNotes) onDeleteNote(n.second); // make sure any plugin-side cleanup is done
activeNotes.clear();
activeNotes.reserve(16+1);
activeTweens.clear();
}
void InstrumentCore::reset() {
release();
activeNotes.reserve(16+1);
activeTweens.reserve(16*2+1);
smpTime = 1.0 / audioEngine->curSampleRate();

View File

@ -93,10 +93,12 @@ namespace Xybrid::NodeLib {
std::function<void(Note&)> onDeleteNote;
InstrumentCore() = default;
~InstrumentCore();
inline double globalTime() const { return time; }
inline double sampleTime() const { return smpTime; }
void release();
void reset();
void process(Data::Node*);
void process(Data::CommandPort*, Data::AudioPort* = nullptr);

View File

@ -170,6 +170,7 @@ void I2x03::init() {
}
void I2x03::reset() { core.reset(); }
void I2x03::release() { core.release(); }
void I2x03::process() { core.process(this); }
void I2x03::saveData(QCborMap& m) const {

View File

@ -3,7 +3,7 @@
#include "nodelib/instrumentcore.h"
namespace Xybrid::Instruments {
class I2x03: public Data::Node {
class I2x03 : public Data::Node {
NodeLib::InstrumentCore core;
NodeLib::ADSR adsr;
@ -22,6 +22,7 @@ namespace Xybrid::Instruments {
void init() override;
void reset() override;
void release() override;
void process() override;
//void onRename() override;

View File

@ -0,0 +1,135 @@
#include "beatpad.h"
using Xybrid::Instruments::BeatPad;
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/patchboard/nodeobject.h"
#include "ui/gadgets/knobgadget.h"
using namespace Xybrid::UI;
#include <cmath>
#include <QDebug>
#include <QCborMap>
#include <QCborValue>
#include <QCborArray>
#define qs QStringLiteral
namespace {
bool _ = PluginRegistry::enqueueRegistration([] {
auto i = std::make_shared<PluginInfo>();
i->id = "plug:beatpad";
i->displayName = "BeatPad";
i->category = "Sampler";
//i->hidden = true;
i->createInstance = []{ return std::make_shared<BeatPad>(); };
PluginRegistry::registerPlugin(i);
//inf = i;
});
}
BeatPad::BeatPad() {
}
void BeatPad::init() {
addPort(Port::Input, Port::Command, 0);
addPort(Port::Output, Port::Audio, 0);
core.onNoteOn = [this](Note& note) {
auto& data = *reinterpret_cast<NoteData*>(&note.scratch);
new (&data) NoteData(); // construct in-place
// look up config for note
if (auto f = cfg.find(static_cast<int16_t>(note.note)); f != cfg.end()) {
data.config = f->second;
note.adsr = {0.0, 0.0, 1.0, shortStep}; // minimal tweening
//
} else core.deleteNote(note); // cancel note if no config found
};
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);
if (!data.config) return core.deleteNote(note);
auto smp = data.config->smp.lock();
if (!smp) return core.deleteNote(note);
double rate = static_cast<double>(smp->sampleRate) / static_cast<double>(audioEngine->curSampleRate());
auto start = data.config->start;
if (start < 0) start = 0;
auto end = data.config->end;
if (end < 0) end = smp->length();
else end = std::min(end, static_cast<decltype(end)>(smp->length()));
size_t ts = p->size;
for (size_t i = 0; i < ts; i++) {
core.advanceNote(note);
// actual sample pos
double sp = static_cast<double>(start) + data.sampleTime * rate;
double ip = std::floor(sp);
double fp = sp - ip;
size_t lutIndex = static_cast<size_t>(fp*NodeLib::LUT_STEPS) % NodeLib::LUT_STEPS;
auto& pt = NodeLib::resamplerLUT[lutIndex];
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 >= start && si < static_cast<ptrdiff_t>(end)) out += (*smp)[static_cast<size_t>(si)] * pt[i];
}
// stuff
(*p)[i] += out.gainBalance(0, note.pan) * note.ampMult();
data.sampleTime += 1;
}
};
}
void BeatPad::reset() { core.reset(); }
void BeatPad::release() { core.release(); }
void BeatPad::process() { core.process(this); }
void BeatPad::saveData(QCborMap& m) const {
//
}
void BeatPad::loadData(const QCborMap& m) {
// TODO: testing
auto c = std::make_shared<NoteConfig>();
cfg.insert_or_assign(60, c);
if (!project->samples.empty()) c->smp = (project->samples.begin()).value();
}
void BeatPad::onGadgetCreated() {
if (!obj) return;
//
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "nodelib/instrumentcore.h"
namespace Xybrid::Data { class Sample; }
namespace Xybrid::Instruments {
class BeatPad : public Data::Node {
NodeLib::InstrumentCore core;
struct NoteConfig {
std::weak_ptr<Data::Sample> smp = std::weak_ptr<Data::Sample>();
double start = -1;
double end = -1;
};
struct NoteData {
std::shared_ptr<NoteConfig> config = nullptr;
double sampleTime = 0;
NoteData() = default;
~NoteData() = default;
};
static_assert (sizeof(NoteData) <= sizeof(NodeLib::InstrumentCore::Note::scratch), "Note data overflows scratch space!");
std::unordered_map<int16_t, std::shared_ptr<NoteConfig>> cfg;
public:
BeatPad();
~BeatPad() 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 onDoubleClick() override;
//void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override;
};
}