beginnings of BeatPad
parent
d2a39a9be6
commit
36e5705377
|
@ -27,7 +27,7 @@ namespace {
|
|||
QHash<QString, std::shared_ptr<PluginInfo>> plugins;
|
||||
|
||||
QString priorityCategories[] {
|
||||
"Gadget", "Instrument", "Effect"
|
||||
"Gadget", "Instrument", "Sampler", "Effect"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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&);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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*>(¬e.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*>(¬e.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*>(¬e.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;
|
||||
|
||||
//
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue