Skip to content

Commit

Permalink
Enable Lv2 Atom ports
Browse files Browse the repository at this point in the history
All Atom ports are now being created and connected. Currently only MIDI
input ports are supplied with data.

Major contribution to LMMS#562 ("lv2 support").
  • Loading branch information
JohannesLorenz committed Oct 20, 2020
1 parent 9c4acd7 commit 247c895
Show file tree
Hide file tree
Showing 13 changed files with 684 additions and 30 deletions.
9 changes: 8 additions & 1 deletion include/Lv2ControlBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ class Lv2ControlBase : public LinkedModelGroups
/*
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
Expand All @@ -123,6 +127,9 @@ class Lv2ControlBase : public LinkedModelGroups
*/
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
Expand Down
149 changes: 149 additions & 0 deletions include/Lv2Evbuf.h
Original file line number Diff line number Diff line change
@@ -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
23 changes: 22 additions & 1 deletion include/Lv2Ports.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "PluginIssue.h"

struct ConnectPortVisitor;
typedef struct LV2_Evbuf_Impl LV2_Evbuf;

namespace Lv2Ports {

Expand All @@ -53,7 +54,7 @@ enum class Type {
Unknown,
Control,
Audio,
Event, //!< TODO: unused, describe
AtomSeq,
Cv //!< TODO: unused, describe
};

Expand All @@ -74,6 +75,7 @@ struct ControlPortBase;
struct Control;
struct Audio;
struct Cv;
struct AtomSeq;
struct Unknown;

struct ConstVisitor
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -192,6 +196,23 @@ struct Audio : public VisitablePort<Audio, PortBase>
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>
{
};
Expand Down
31 changes: 29 additions & 2 deletions include/Lv2Proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -106,8 +109,11 @@ class Lv2Proc : public LinkedModelGroup
/*
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
Expand Down Expand Up @@ -137,11 +143,15 @@ class Lv2Proc : public LinkedModelGroup
//! 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:
/*
Expand All @@ -159,8 +169,25 @@ class Lv2Proc : public LinkedModelGroup
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;
Expand Down
2 changes: 1 addition & 1 deletion include/Lv2UridCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions plugins/Lv2Effect/Lv2Effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 4 additions & 5 deletions plugins/Lv2Instrument/Lv2Instrument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -161,6 +159,7 @@ void Lv2Instrument::play(sampleFrame *buf)

run(fpp);

copyModelsToLmms();
copyBuffersToLmms(buf, fpp);

instrumentTrack()->processAudioBuffer(buf, fpp, nullptr);
Expand Down
2 changes: 1 addition & 1 deletion plugins/Lv2Instrument/Lv2Instrument.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Lv2Instrument : public Instrument, public Lv2ControlBase
/*
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;
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 247c895

Please sign in to comment.