Enable Lv2 Atom ports

All Atom ports are now being created and connected. Currently only MIDI
input ports are supplied with data.

Major contribution to #562 ("lv2 support").
This commit is contained in:
Johannes Lorenz
2019-06-02 19:23:31 +02:00
committed by Johannes Lorenz
parent acd0e4d430
commit 8b2902c27a
13 changed files with 686 additions and 32 deletions

View File

@@ -100,8 +100,12 @@ protected:
/*
utils for the run thread
*/
//! Copy values from all connected models into the respective ports
//! Copy values from the LMMS core (connected models, MIDI events, ...) into
//! the respective ports
void copyModelsFromLmms();
//! Bring values from all ports to the LMMS core
void copyModelsToLmms() const;
//! Copy buffer passed by LMMS into our ports
void copyBuffersFromLmms(const sampleFrame *buf, fpp_t frames);
//! Copy our ports into buffers passed by LMMS
@@ -123,6 +127,9 @@ protected:
*/
std::size_t controlCount() const;
QString nodeName() const { return "lv2controls"; }
bool hasNoteInput() const;
void handleMidiInputEvent(const class MidiEvent &event,
const class MidiTime &time, f_cnt_t offset);
private:
//! Return the DataFile settings type

149
include/Lv2Evbuf.h Normal file
View File

@@ -0,0 +1,149 @@
/*
* lv2_evbuf.h - Lv2 event buffer definitions
*
* Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
/*
* The original code was written by David Robillard <http://drobilla.net>
* Original version: 6f22ee0 from https://github.com/drobilla/jalv.git
* Minor changes have been done, but no functional changes.
* Considering this as an "external library", the identifiers do not need to
* match the LMMS coding conventions.
*/
#ifndef LV2_EVBUF_H
#define LV2_EVBUF_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <cstdint>
/**
An abstract/opaque LV2 event buffer.
*/
typedef struct LV2_Evbuf_Impl LV2_Evbuf;
/**
An iterator over an LV2_Evbuf.
*/
typedef struct {
LV2_Evbuf* evbuf;
uint32_t offset;
} LV2_Evbuf_Iterator;
/**
Allocate a new, empty event buffer.
URIDs for atom:Chunk and atom:Sequence must be passed for LV2_EVBUF_ATOM.
*/
LV2_Evbuf*
lv2_evbuf_new(uint32_t capacity, uint32_t atom_Chunk, uint32_t atom_Sequence);
/**
Free an event buffer allocated with lv2_evbuf_new.
*/
void
lv2_evbuf_free(LV2_Evbuf* evbuf);
/**
Clear and initialize an existing event buffer.
The contents of buf are ignored entirely and overwritten, except capacity
which is unmodified.
If input is false and this is an atom buffer, the buffer will be prepared
for writing by the plugin. This MUST be called before every run cycle.
*/
void
lv2_evbuf_reset(LV2_Evbuf* evbuf, bool input);
/**
Return the total padded size of the events stored in the buffer.
*/
uint32_t
lv2_evbuf_get_size(LV2_Evbuf* evbuf);
/**
Return the actual buffer implementation.
The format of the buffer returned depends on the buffer type.
*/
void*
lv2_evbuf_get_buffer(LV2_Evbuf* evbuf);
/**
Return an iterator to the start of `evbuf`.
*/
LV2_Evbuf_Iterator
lv2_evbuf_begin(LV2_Evbuf* evbuf);
/**
Return an iterator to the end of `evbuf`.
*/
LV2_Evbuf_Iterator
lv2_evbuf_end(LV2_Evbuf* evbuf);
/**
Check if `iter` is valid.
@return True if `iter` is valid, otherwise false (past end of buffer)
*/
bool
lv2_evbuf_is_valid(LV2_Evbuf_Iterator iter);
/**
Advance `iter` forward one event.
`iter` must be valid.
@return True if `iter` is valid, otherwise false (reached end of buffer)
*/
LV2_Evbuf_Iterator
lv2_evbuf_next(LV2_Evbuf_Iterator iter);
/**
Dereference an event iterator (i.e. get the event currently pointed to).
`iter` must be valid.
`type` Set to the type of the event.
`size` Set to the size of the event.
`data` Set to the contents of the event.
@return True on success.
*/
bool
lv2_evbuf_get( LV2_Evbuf_Iterator iter,
uint32_t* frames,
uint32_t* type,
uint32_t* size,
uint8_t** data);
/**
Write an event at `iter`.
The event (if any) pointed to by `iter` will be overwritten, and `iter`
incremented to point to the following event (i.e. several calls to this
function can be done in sequence without twiddling iter in-between).
@return True if event was written, otherwise false (buffer is full).
*/
bool
lv2_evbuf_write( LV2_Evbuf_Iterator* iter,
uint32_t frames,
uint32_t type,
uint32_t size,
const uint8_t* data);
#endif // LMMS_HAVE_LV2
#endif // LV2_EVBUF_H

View File

@@ -37,6 +37,7 @@
#include "PluginIssue.h"
struct ConnectPortVisitor;
typedef struct LV2_Evbuf_Impl LV2_Evbuf;
namespace Lv2Ports {
@@ -53,7 +54,7 @@ enum class Type {
Unknown,
Control,
Audio,
Event, //!< TODO: unused, describe
AtomSeq,
Cv //!< TODO: unused, describe
};
@@ -74,6 +75,7 @@ struct ControlPortBase;
struct Control;
struct Audio;
struct Cv;
struct AtomSeq;
struct Unknown;
struct ConstVisitor
@@ -82,6 +84,7 @@ struct ConstVisitor
virtual void visit(const Lv2Ports::Control& ) {}
virtual void visit(const Lv2Ports::Audio& ) {}
virtual void visit(const Lv2Ports::Cv& ) {}
virtual void visit(const Lv2Ports::AtomSeq& ) {}
virtual void visit(const Lv2Ports::Unknown& ) {}
virtual ~ConstVisitor();
@@ -93,6 +96,7 @@ struct Visitor
virtual void visit(Lv2Ports::Control& ) {}
virtual void visit(Lv2Ports::Audio& ) {}
virtual void visit(Lv2Ports::Cv& ) {}
virtual void visit(Lv2Ports::AtomSeq& ) {}
virtual void visit(Lv2Ports::Unknown& ) {}
virtual ~Visitor();
@@ -192,6 +196,23 @@ private:
friend struct ::ConnectPortVisitor;
};
struct AtomSeq : public VisitablePort<AtomSeq, PortBase>
{
enum FlagType
{
None = 0,
Midi = 1
};
unsigned flags = FlagType::None;
struct Lv2EvbufDeleter
{
void operator()(LV2_Evbuf* n);
};
using AutoLv2Evbuf = std::unique_ptr<LV2_Evbuf, Lv2EvbufDeleter>;
AutoLv2Evbuf m_buf;
};
struct Unknown : public VisitablePort<Unknown, PortBase>
{
};

View File

@@ -36,15 +36,18 @@
#include "Lv2Basics.h"
#include "Lv2Features.h"
#include "LinkedModelGroups.h"
#include "MidiEvent.h"
#include "MidiTime.h"
#include "Plugin.h"
#include "PluginIssue.h"
#include "../src/3rdparty/ringbuffer/include/ringbuffer/ringbuffer.h"
// forward declare port structs/enums
namespace Lv2Ports
{
struct Audio;
struct PortBase;
struct AtomSeq;
enum class Type;
enum class Flow;
@@ -106,8 +109,11 @@ public:
/*
utils for the run thread
*/
//! Copy values from all connected models into the respective ports
//! Copy values from the LMMS core (connected models, MIDI events, ...) into
//! the respective ports
void copyModelsFromCore();
//! Bring values from all ports to the LMMS core
void copyModelsToCore();
/**
* Copy buffer passed by the core into our ports
* @param buf buffer of sample frames, each sample frame is something like
@@ -137,11 +143,15 @@ public:
//! Run the Lv2 plugin instance for @param frames frames
void run(fpp_t frames);
void handleMidiInputEvent(const class MidiEvent &event,
const MidiTime &time, f_cnt_t offset);
/*
misc
*/
class AutomatableModel *modelAtPort(const QString &uri); // unused currently
std::size_t controlCount() const { return LinkedModelGroup::modelNum(); }
bool hasNoteInput() const;
protected:
/*
@@ -159,8 +169,25 @@ private:
LilvInstance* m_instance;
Lv2Features m_features;
// full list of ports
std::vector<std::unique_ptr<Lv2Ports::PortBase>> m_ports;
// quick reference to specific, unique ports
StereoPortRef m_inPorts, m_outPorts;
Lv2Ports::AtomSeq *m_midiIn = nullptr, *m_midiOut = nullptr;
// MIDI
// many things here may be moved into the `Instrument` class
constexpr const static std::size_t m_maxMidiInputEvents = 1024;
//! spinlock for the MIDI ringbuffer (for MIDI events going to the plugin)
std::atomic_flag m_ringLock = ATOMIC_FLAG_INIT;
//! MIDI ringbuffer (for MIDI events going to the plugin)
ringbuffer_t<struct MidiInputEvent> m_midiInputBuf;
//! MIDI ringbuffer reader
ringbuffer_reader_t<struct MidiInputEvent> m_midiInputReader;
// other
static std::size_t minimumEvbufSize() { return 1 << 15; /* ardour uses this*/ }
//! models for the controls, sorted by port symbols
std::map<std::string, AutomatableModel *> m_connectedModels;

View File

@@ -37,7 +37,7 @@ class Lv2UridCache
public:
enum class Id //!< ID for m_uridCache array
{
midi_MidiEvent, //!< just an example, unused yet
midi_MidiEvent,
size
};
//! Return URID for a cache ID

View File

@@ -74,6 +74,7 @@ bool Lv2Effect::processAudioBuffer(sampleFrame *buf, const fpp_t frames)
m_controls.run(frames);
// m_pluginMutex.unlock();
m_controls.copyModelsToLmms();
m_controls.copyBuffersToLmms(m_tmpOutputSmps.data(), frames);
double outSum = .0;

View File

@@ -131,11 +131,9 @@ void Lv2Instrument::loadFile(const QString &file)
bool Lv2Instrument::handleMidiEvent(
const MidiEvent &event, const MidiTime &time, f_cnt_t offset)
{
// this function can be called from GUI threads while the plugin is running,
// so this requires caching, e.g. in ringbuffers
(void)time;
(void)offset;
(void)event;
// this function can be called from GUI threads while the plugin is running
// handleMidiInputEvent will use a thread-safe ringbuffer
handleMidiInputEvent(event, time, offset);
return true;
}
#endif
@@ -161,6 +159,7 @@ void Lv2Instrument::play(sampleFrame *buf)
run(fpp);
copyModelsToLmms();
copyBuffersToLmms(buf, fpp);
instrumentTrack()->processAudioBuffer(buf, fpp, nullptr);

View File

@@ -63,7 +63,7 @@ public:
/*
realtime funcs
*/
bool hasNoteInput() const override { return false; /* not supported yet */ }
bool hasNoteInput() const override { return Lv2ControlBase::hasNoteInput(); }
#ifdef LV2_INSTRUMENT_USE_MIDI
bool handleMidiEvent(const MidiEvent &event,
const MidiTime &time = MidiTime(), f_cnt_t offset = 0) override;

View File

@@ -93,6 +93,7 @@ set(LMMS_SRCS
core/lv2/Lv2Basics.cpp
core/lv2/Lv2ControlBase.cpp
core/lv2/Lv2Evbuf.cpp
core/lv2/Lv2Features.cpp
core/lv2/Lv2Ports.cpp
core/lv2/Lv2Proc.cpp

View File

@@ -26,6 +26,7 @@
#ifdef LMMS_HAVE_LV2
#include <algorithm>
#include <QtGlobal>
#include "Engine.h"
@@ -113,6 +114,14 @@ void Lv2ControlBase::copyModelsFromLmms() {
void Lv2ControlBase::copyModelsToLmms() const
{
for (auto& c : m_procs) { c->copyModelsToCore(); }
}
void Lv2ControlBase::copyBuffersFromLmms(const sampleFrame *buf, fpp_t frames) {
unsigned firstChan = 0; // tell the procs which channels they shall read from
for (auto& c : m_procs) {
@@ -187,4 +196,22 @@ std::size_t Lv2ControlBase::controlCount() const {
bool Lv2ControlBase::hasNoteInput() const
{
return std::any_of(m_procs.begin(), m_procs.end(),
[](const auto& c) { return c->hasNoteInput(); });
}
void Lv2ControlBase::handleMidiInputEvent(const MidiEvent &event,
const MidiTime &time, f_cnt_t offset)
{
for (auto& c : m_procs) { c->handleMidiInputEvent(event, time, offset); }
}
#endif // LMMS_HAVE_LV2

193
src/core/lv2/Lv2Evbuf.cpp Normal file
View File

@@ -0,0 +1,193 @@
/*
* lv2_evbuf.cpp - Lv2 event buffer implementation
*
* Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
/*
* The original code was written by David Robillard <http://drobilla.net>
*/
#include "Lv2Evbuf.h"
#ifdef LMMS_HAVE_LV2
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
struct LV2_Evbuf_Impl {
uint32_t capacity;
uint32_t atom_Chunk;
uint32_t atom_Sequence;
LV2_Atom_Sequence buf;
};
static inline uint32_t
lv2_evbuf_pad_size(uint32_t size)
{
return (size + 7) & (~7);
}
LV2_Evbuf*
lv2_evbuf_new(uint32_t capacity, uint32_t atom_Chunk, uint32_t atom_Sequence)
{
// FIXME: memory must be 64-bit aligned
LV2_Evbuf* evbuf = (LV2_Evbuf*)malloc(
sizeof(LV2_Evbuf) + sizeof(LV2_Atom_Sequence) + capacity);
evbuf->capacity = capacity;
evbuf->atom_Chunk = atom_Chunk;
evbuf->atom_Sequence = atom_Sequence;
lv2_evbuf_reset(evbuf, true);
return evbuf;
}
void
lv2_evbuf_free(LV2_Evbuf* evbuf)
{
free(evbuf);
}
void
lv2_evbuf_reset(LV2_Evbuf* evbuf, bool input)
{
if (input) {
evbuf->buf.atom.size = sizeof(LV2_Atom_Sequence_Body);
evbuf->buf.atom.type = evbuf->atom_Sequence;
} else {
evbuf->buf.atom.size = evbuf->capacity;
evbuf->buf.atom.type = evbuf->atom_Chunk;
}
}
uint32_t
lv2_evbuf_get_size(LV2_Evbuf* evbuf)
{
assert(evbuf->buf.atom.type != evbuf->atom_Sequence
|| evbuf->buf.atom.size >= sizeof(LV2_Atom_Sequence_Body));
return evbuf->buf.atom.type == evbuf->atom_Sequence
? evbuf->buf.atom.size - sizeof(LV2_Atom_Sequence_Body)
: 0;
}
void*
lv2_evbuf_get_buffer(LV2_Evbuf* evbuf)
{
return &evbuf->buf;
}
LV2_Evbuf_Iterator
lv2_evbuf_begin(LV2_Evbuf* evbuf)
{
LV2_Evbuf_Iterator iter = { evbuf, 0 };
return iter;
}
LV2_Evbuf_Iterator
lv2_evbuf_end(LV2_Evbuf* evbuf)
{
const uint32_t size = lv2_evbuf_get_size(evbuf);
const LV2_Evbuf_Iterator iter = { evbuf, lv2_evbuf_pad_size(size) };
return iter;
}
bool
lv2_evbuf_is_valid(LV2_Evbuf_Iterator iter)
{
return iter.offset < lv2_evbuf_get_size(iter.evbuf);
}
LV2_Evbuf_Iterator
lv2_evbuf_next(LV2_Evbuf_Iterator iter)
{
if (!lv2_evbuf_is_valid(iter)) {
return iter;
}
LV2_Evbuf* evbuf = iter.evbuf;
uint32_t offset = iter.offset;
uint32_t size;
size = ((LV2_Atom_Event*)
((char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, &evbuf->buf.atom)
+ offset))->body.size;
offset += lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size);
LV2_Evbuf_Iterator next = { evbuf, offset };
return next;
}
bool
lv2_evbuf_get(LV2_Evbuf_Iterator iter,
uint32_t* frames,
uint32_t* type,
uint32_t* size,
uint8_t** data)
{
*frames = *type = *size = 0;
*data = NULL;
if (!lv2_evbuf_is_valid(iter)) {
return false;
}
LV2_Atom_Sequence* aseq = &iter.evbuf->buf;
LV2_Atom_Event* aev = (LV2_Atom_Event*)(
(char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + iter.offset);
*frames = aev->time.frames;
*type = aev->body.type;
*size = aev->body.size;
*data = (uint8_t*)LV2_ATOM_BODY(&aev->body);
return true;
}
bool
lv2_evbuf_write(LV2_Evbuf_Iterator* iter,
uint32_t frames,
uint32_t type,
uint32_t size,
const uint8_t* data)
{
LV2_Atom_Sequence* aseq = &iter->evbuf->buf;
if (iter->evbuf->capacity - sizeof(LV2_Atom) - aseq->atom.size <
sizeof(LV2_Atom_Event) + size) {
return false;
}
LV2_Atom_Event* aev = (LV2_Atom_Event*)(
(char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, aseq) + iter->offset);
aev->time.frames = frames;
aev->body.type = type;
aev->body.size = size;
memcpy(LV2_ATOM_BODY(&aev->body), data, size);
size = lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size);
aseq->atom.size += size;
iter->offset += size;
return true;
}
#endif // LMMS_HAVE_LV2

View File

@@ -27,9 +27,12 @@
#ifdef LMMS_HAVE_LV2
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include "Engine.h"
#include "Lv2Basics.h"
#include "Lv2Manager.h"
#include "Lv2Evbuf.h"
namespace Lv2Ports {
@@ -57,7 +60,7 @@ const char *toStr(Type pt)
case Type::Unknown: return "unknown";
case Type::Control: return "control";
case Type::Audio: return "audio";
case Type::Event: return "event";
case Type::AtomSeq: return "atom-sequence";
case Type::Cv: return "cv";
}
return "";
@@ -168,6 +171,23 @@ std::vector<PluginIssue> Meta::get(const LilvPlugin *plugin,
issue(badPortType, "cvPort");
m_type = Type::Cv;
}
else if (isA(LV2_ATOM__AtomPort))
{
AutoLilvNode uriAtomSequence(Engine::getLv2Manager()->uri(LV2_ATOM__Sequence));
AutoLilvNode uriAtomBufferType(Engine::getLv2Manager()->uri(LV2_ATOM__bufferType));
AutoLilvNodes bufferTypes(lilv_port_get_value(plugin, lilvPort, uriAtomBufferType.get()));
if (lilv_nodes_contains(bufferTypes.get(), uriAtomSequence.get()))
{
// we accept all kinds of atom sequence ports, even if they take or
// offer atom types that we do not support:
// * atom input ports only say what *can* be input, but not what is
// required as input
// * atom output ports only say what *can* be output, but not what must
// be evaluated
m_type = Type::AtomSeq;
}
}
if(m_type == Type::Unknown)
{
@@ -245,6 +265,11 @@ void Audio::copyBuffersToCore(sampleFrame *lmmsBuf,
void AtomSeq::Lv2EvbufDeleter::operator()(LV2_Evbuf *n) { lv2_evbuf_free(n); }
// make the compiler happy, give each class with virtuals
// a function (the destructor here) which is in a cpp file
PortBase::~PortBase() {}

View File

@@ -27,6 +27,11 @@
#ifdef LMMS_HAVE_LV2
#include <cmath>
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/resize-port/resize-port.h>
#include <QDebug>
#include <QtGlobal>
#include "AutomatableModel.h"
#include "ComboBoxModel.h"
@@ -34,17 +39,31 @@
#include "Lv2Features.h"
#include "Lv2Manager.h"
#include "Lv2Ports.h"
#include "Lv2Evbuf.h"
#include "MidiEventToByteSeq.h"
#include "Mixer.h"
// container for everything required to store MIDI events going to the plugin
struct MidiInputEvent
{
MidiEvent ev;
MidiTime time;
f_cnt_t offset;
};
Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin,
std::vector<PluginIssue>& issues, bool printIssues)
{
unsigned maxPorts = lilv_plugin_get_num_ports(plugin);
enum { inCount, outCount, maxCount };
unsigned audioChannels[maxCount] = { 0, 0 }; // input and output count
unsigned audioChannels[maxCount] = { 0, 0 }; // audio input and output count
unsigned midiChannels[maxCount] = { 0, 0 }; // MIDI input and output count
for (unsigned portNum = 0; portNum < maxPorts; ++portNum)
{
@@ -58,8 +77,15 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin,
lilv_plugin_get_port_by_index(plugin, portNum)) &&
!meta.m_optional;
if (meta.m_type == Lv2Ports::Type::Audio && portMustBeUsed)
{
++audioChannels[meta.m_flow == Lv2Ports::Flow::Output
? outCount : inCount];
}
else if(meta.m_type == Lv2Ports::Type::AtomSeq && portMustBeUsed)
{
++midiChannels[meta.m_flow == Lv2Ports::Flow::Output
? outCount : inCount];
}
}
if (audioChannels[inCount] > 2)
@@ -71,6 +97,13 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin,
issues.emplace_back(tooManyOutputChannels,
std::to_string(audioChannels[outCount]));
if (midiChannels[inCount] > 1)
issues.emplace_back(tooManyMidiInputChannels,
std::to_string(midiChannels[inCount]));
if (midiChannels[outCount] > 1)
issues.emplace_back(tooManyMidiOutputChannels,
std::to_string(midiChannels[outCount]));
AutoLilvNodes reqFeats(lilv_plugin_get_required_features(plugin));
LILV_FOREACH (nodes, itr, reqFeats.get())
{
@@ -104,7 +137,9 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin,
Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
LinkedModelGroup(parent),
m_plugin(plugin)
m_plugin(plugin),
m_midiInputBuf(m_maxMidiInputEvents),
m_midiInputReader(m_midiInputBuf)
{
initPlugin();
}
@@ -149,29 +184,79 @@ void Lv2Proc::copyModelsFromCore()
{
void visit(Lv2Ports::Control& ctrl) override
{
if (ctrl.m_flow == Lv2Ports::Flow::Input)
{
FloatFromModelVisitor ffm;
ffm.m_scalePointMap = &ctrl.m_scalePointMap;
ctrl.m_connectedModel->accept(ffm);
ctrl.m_val = ffm.m_res;
}
FloatFromModelVisitor ffm;
ffm.m_scalePointMap = &ctrl.m_scalePointMap;
ctrl.m_connectedModel->accept(ffm);
ctrl.m_val = ffm.m_res;
}
void visit(Lv2Ports::Cv& cv) override
{
if (cv.m_flow == Lv2Ports::Flow::Input)
{
FloatFromModelVisitor ffm;
ffm.m_scalePointMap = &cv.m_scalePointMap;
cv.m_connectedModel->accept(ffm);
// dirty fix, needs better interpolation
std::fill(cv.m_buffer.begin(), cv.m_buffer.end(), ffm.m_res);
}
FloatFromModelVisitor ffm;
ffm.m_scalePointMap = &cv.m_scalePointMap;
cv.m_connectedModel->accept(ffm);
// dirty fix, needs better interpolation
std::fill(cv.m_buffer.begin(), cv.m_buffer.end(), ffm.m_res);
}
void visit(Lv2Ports::AtomSeq& atomPort) override
{
lv2_evbuf_reset(atomPort.m_buf.get(), true);
}
} copy;
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports) {
port->accept(copy); }
// feed each input port with the respective data from the LMMS core
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
{
if (port->m_flow == Lv2Ports::Flow::Input)
{
port->accept(copy);
}
}
// send pending MIDI events to atom port
if(m_midiIn)
{
LV2_Evbuf_Iterator iter = lv2_evbuf_begin(m_midiIn->m_buf.get());
// MIDI events waiting to go to the plugin?
while(m_midiInputReader.read_space() > 0)
{
const MidiInputEvent ev = m_midiInputReader.read(1)[0];
uint32_t atomStamp =
ev.time.frames(Engine::framesPerTick()) + ev.offset;
uint32_t type = Engine::getLv2Manager()->
uridCache()[Lv2UridCache::Id::midi_MidiEvent];
uint8_t buf[4];
std::size_t bufsize = writeToByteSeq(ev.ev, buf, sizeof(buf));
if(bufsize)
{
lv2_evbuf_write(&iter, atomStamp, type, bufsize, buf);
}
}
}
}
void Lv2Proc::copyModelsToCore()
{
struct Copy : public Lv2Ports::Visitor
{
void visit(Lv2Ports::AtomSeq& atomPort) override
{
// we currently don't copy anything, but we need to clear the buffer
// for the plugin to write again
lv2_evbuf_reset(atomPort.m_buf.get(), false);
}
} copy;
// fetch data from each output port and bring it to the LMMS core
for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
{
if (port->m_flow == Lv2Ports::Flow::Output)
{
port->accept(copy);
}
}
}
@@ -229,6 +314,41 @@ void Lv2Proc::run(fpp_t frames)
// in case there will be a PR which removes this callback and instead adds a
// `ringbuffer_t<MidiEvent + time info>` to `class Instrument`, this
// function (and the ringbuffer and its reader in `Lv2Proc`) will simply vanish
void Lv2Proc::handleMidiInputEvent(const MidiEvent &event, const MidiTime &time, f_cnt_t offset)
{
if(m_midiIn)
{
// ringbuffer allows only one writer at a time
// however, this function can be called by multiple threads
// (different RT and non-RT!) at the same time
// for now, a spinlock looks like the most safe/easy compromise
// source: https://en.cppreference.com/w/cpp/atomic/atomic_flag
while (m_ringLock.test_and_set(std::memory_order_acquire)) // acquire lock
; // spin
MidiInputEvent ev { event, time, offset };
std::size_t written = m_midiInputBuf.write(&ev, 1);
if(written != 1)
{
qWarning("MIDI ringbuffer is too small! Discarding MIDI event.");
}
m_ringLock.clear(std::memory_order_release);
}
else
{
qWarning() << "Warning: Caught MIDI event for an Lv2 instrument"
<< "that can not hande MIDI... Ignoring";
}
}
AutomatableModel *Lv2Proc::modelAtPort(const QString &uri)
{
// unused currently
@@ -284,6 +404,18 @@ void Lv2Proc::shutdownPlugin()
bool Lv2Proc::hasNoteInput() const
{
return m_midiIn;
// we could additionally check for
// http://lv2plug.in/ns/lv2core#InstrumentPlugin
// however, jalv does not do that, too
// so, if there's any MIDI input, we just assume we can send notes there
}
void Lv2Proc::initPluginSpecificFeatures()
{
// nothing yet
@@ -385,6 +517,46 @@ void Lv2Proc::createPort(std::size_t portNum)
port = audio;
break;
}
case Lv2Ports::Type::AtomSeq:
{
Lv2Ports::AtomSeq* atomPort = new Lv2Ports::AtomSeq;
{
AutoLilvNode uriAtomSupports(Engine::getLv2Manager()->uri(LV2_ATOM__supports));
AutoLilvNodes atomSupports(lilv_port_get_value(m_plugin, lilvPort, uriAtomSupports.get()));
AutoLilvNode uriMidiEvent(Engine::getLv2Manager()->uri(LV2_MIDI__MidiEvent));
LILV_FOREACH (nodes, itr, atomSupports.get())
{
if(lilv_node_equals(lilv_nodes_get(atomSupports.get(), itr), uriMidiEvent.get()))
{
atomPort->flags |= Lv2Ports::AtomSeq::FlagType::Midi;
}
}
}
int minimumSize = minimumEvbufSize();
Lv2Manager* mgr = Engine::getLv2Manager();
// check for alternative minimum size
{
AutoLilvNode rszMinimumSize = mgr->uri(LV2_RESIZE_PORT__minimumSize);
AutoLilvNodes minSizeV(lilv_port_get_value(m_plugin, lilvPort, rszMinimumSize.get()));
LilvNode* minSize = minSizeV ? lilv_nodes_get_first(minSizeV.get()) : nullptr;
if (minSize && lilv_node_is_int(minSize)) {
minimumSize = std::max(minimumSize, lilv_node_as_int(minSize));
}
}
atomPort->m_buf.reset(
lv2_evbuf_new(static_cast<uint32_t>(minimumSize),
mgr->uridMap().map(LV2_ATOM__Chunk),
mgr->uridMap().map(LV2_ATOM__Sequence)));
port = atomPort;
break;
}
default:
port = new Lv2Ports::Unknown;
}
@@ -445,6 +617,33 @@ void Lv2Proc::createPorts()
else if (!portRef->m_right) { portRef->m_right = &audio; }
}
}
void visit(Lv2Ports::AtomSeq& atomPort) override
{
if(atomPort.m_flow == Lv2Ports::Flow::Input)
{
if(atomPort.flags & Lv2Ports::AtomSeq::FlagType::Midi)
{
// take any MIDI input, prefer mandatory MIDI input
// (Lv2Proc::check() assures there are <=1 mandatory MIDI
// input ports)
if(!m_proc->m_midiIn || !atomPort.m_optional)
m_proc->m_midiIn = &atomPort;
}
}
else if(atomPort.m_flow == Lv2Ports::Flow::Output)
{
if(atomPort.flags & Lv2Ports::AtomSeq::FlagType::Midi)
{
// take any MIDI output, prefer mandatory MIDI output
// (Lv2Proc::check() assures there are <=1 mandatory MIDI
// output ports)
if(!m_proc->m_midiOut || !atomPort.m_optional)
m_proc->m_midiOut = &atomPort;
}
}
else { Q_ASSERT(false); }
}
};
std::size_t maxPorts = lilv_plugin_get_num_ports(m_plugin);
@@ -472,10 +671,15 @@ struct ConnectPortVisitor : public Lv2Ports::Visitor
{
std::size_t m_num;
LilvInstance* m_instance;
void connectPort(void* location) {
void connectPort(void* location)
{
lilv_instance_connect_port(m_instance,
static_cast<uint32_t>(m_num), location);
}
void visit(Lv2Ports::AtomSeq& atomSeq) override
{
connectPort(lv2_evbuf_get_buffer(atomSeq.m_buf.get()));
}
void visit(Lv2Ports::Control& ctrl) override { connectPort(&ctrl.m_val); }
void visit(Lv2Ports::Audio& audio) override
{