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:
committed by
Johannes Lorenz
parent
acd0e4d430
commit
8b2902c27a
@@ -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
149
include/Lv2Evbuf.h
Normal 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
|
||||
@@ -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>
|
||||
{
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
193
src/core/lv2/Lv2Evbuf.cpp
Normal 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
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user