summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2016-03-11 12:28:44 +0100
committerBent Bisballe Nyeng <deva@aasimon.org>2016-03-11 12:28:44 +0100
commita27d71e88bd8f950e11e7a1a2ba0a4dd88f541f4 (patch)
tree688bbe1368d31bb7a67f29795a040c1102c34a6e
Initial import from DrumGizmo branch.
-rw-r--r--COPYING165
-rw-r--r--Makefile64
-rwxr-xr-xadd_file64
-rw-r--r--manifest.ttl110
-rw-r--r--midievent.cc71
-rw-r--r--midievent.h56
-rw-r--r--plugin.h145
-rw-r--r--pluginlv2.cc637
-rw-r--r--pluginlv2.h225
-rw-r--r--plugintest.cc334
-rw-r--r--plugintest.h113
-rw-r--r--pluginvst.cc391
-rw-r--r--pluginvst.h208
13 files changed, 2583 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..65c5ca8
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a630927
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,64 @@
+PLUGIN_NAME=plugintest
+PLUGIN_URI=http://example.org/plugintest
+
+CXXFLAGS = -DX11 -I. -fPIC -std=c++11 -Wall -shared -Wl,--no-undefined
+
+LV2_CXXFLAGS = -DLV2 -g -Werror -DLV2_PLUGIN_URI=\"$(PLUGIN_URI)\"
+LV2_SRC = \
+ pluginlv2.cc \
+ midievent.cc \
+ plugintest.cc
+
+
+VST_PATH=/path/to/vstsdk2.4
+VST_SRC_BASE=$(VST_PATH)/public.sdk/source/vst2.x/
+VST_CXXFLAGS=-DVST -I$(VST_PATH)
+VST_SRC = \
+ ${VST_SRC_BASE}/audioeffectx.cpp \
+ ${VST_SRC_BASE}/audioeffect.cpp \
+ ${VST_SRC_BASE}/vstplugmain.cpp \
+ pluginvst.cc \
+ midievent.cc \
+ plugintest.cc
+
+## LV2 targets
+
+$(PLUGIN_NAME)_lv2.so: $(LV2_SRC)
+ g++ $(CXXFLAGS) $(LV2_CXXFLAGS) $(LV2_SRC) -lX11 -o$(PLUGIN_NAME)_lv2.so
+
+install-lv2: $(PLUGIN_NAME)_lv2.so
+ mkdir -p lib/lv2/$(PLUGIN_NAME)
+ cp manifest.ttl $(PLUGIN_NAME)_lv2.so lib/lv2/$(PLUGIN_NAME)
+
+test-lv2: install-lv2
+ LV2_PATH="$(PWD)/lib/lv2" lv2_inspect $(PLUGIN_URI)
+ carla-discovery-native lv2 $(PWD)/lib/lv2/$(PLUGIN_NAME)
+
+clean-lv2:
+ rm -f $(PLUGIN_NAME)_lv2.so
+
+## VST targets
+
+$(PLUGIN_NAME)_vst.so: $(VST_SRC)
+ [ "$VST_PATH" == "" ] && (echo "Run make with 'VST_PATH=/path/to/vstsdk2.4 make'"; false)
+ g++ $(CXXFLAGS) $(VST_CXXFLAGS) $(VST_SRC) -lX11 -o$(PLUGIN_NAME)_vst.so
+
+install-vst: $(PLUGIN_NAME)_vst.so
+ mkdir -p lib/vst/$(PLUGIN_NAME)
+ cp $(PLUGIN_NAME)_vst.so lib/vst/$(PLUGIN_NAME)
+
+test-vst: install-vst
+ carla-discovery-native vst lib/vst/$(PLUGIN_NAME)/$(PLUGIN_NAME)_vst.so
+
+clean-vst:
+ rm -f $(PLUGIN_NAME)_vst.so
+
+## Common targets
+
+install: install-lv2 install-vst
+
+test: test-lv2 test-vst
+
+all: $(PLUGIN_NAME)_lv2.so $(PLUGIN_NAME)_vst.so
+
+clean: clean-lv2 clean-vst \ No newline at end of file
diff --git a/add_file b/add_file
new file mode 100755
index 0000000..8043794
--- /dev/null
+++ b/add_file
@@ -0,0 +1,64 @@
+#!/bin/bash
+PROJECT="PluginGizmo"
+
+function allfile() {
+ if [ "$USER" == "deva" ]
+ then
+ NAME="Bent Bisballe Nyeng"; EMAIL="deva@aasimon.org"
+ fi
+
+ echo "/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */" > $1;
+ echo "/***************************************************************************" >> $1;
+ echo " * $1" >> $1;
+ echo " *" >> $1 ;
+ echo " * `date`" >> $1;
+ echo " * Copyright "`date +%Y | xargs`" $NAME" >> $1;
+ echo " * $EMAIL" >> $1;
+ echo " ****************************************************************************/" >> $1;
+ echo "" >> $1;
+ echo "/*" >> $1;
+ echo " * This file is part of $PROJECT." >> $1;
+ echo " *" >> $1;
+ echo " * $PROJECT is free software; you can redistribute it and/or modify" >> $1;
+ echo " * it under the terms of the GNU Lesser General Public License as published by" >> $1;
+ echo " * the Free Software Foundation; either version 3 of the License, or" >> $1;
+ echo " * (at your option) any later version." >> $1;
+ echo " *" >> $1;
+ echo " * $PROJECT is distributed in the hope that it will be useful," >> $1;
+ echo " * but WITHOUT ANY WARRANTY; without even the implied warranty of" >> $1;
+ echo " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" >> $1;
+ echo " * GNU Lesser General Public License for more details." >> $1;
+ echo " *" >> $1;
+ echo " * You should have received a copy of the GNU Lesser General Public License" >> $1;
+ echo " * along with $PROJECT; if not, write to the Free Software" >> $1;
+ echo " * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA." >> $1;
+ echo " */" >> $1;
+}
+
+function ccfile() {
+ local hf=`echo -n $1 | cut -d'.' -f1`.h;
+ hfile $hf;
+
+ allfile $1;
+ echo -n '#include "' >> $1;
+ echo -n $hf >> $1;
+ echo '"' >> $1;
+ echo '' >> $1;
+}
+
+function hfile() {
+ allfile $1;
+ local hn=`echo $1 | tr 'a-z.' 'A-Z_'`
+ local pr=`echo $PROJECT | tr 'a-z.' 'A-Z_'`
+ echo "#pragma once" >> $1;
+}
+
+if [ "$#" = "1" ]; then
+if [ "CC" = `echo $1 | cut -d'.' -f2 | tr 'a-z' 'A-Z'` ]; then
+ ccfile $1;
+fi;
+if [ "H" = `echo $1 | cut -d'.' -f2 | tr 'a-z' 'A-Z'` ]; then
+ hfile $1;
+fi;
+else echo "Usage: $0 filename";
+fi;
diff --git a/manifest.ttl b/manifest.ttl
new file mode 100644
index 0000000..61304ed
--- /dev/null
+++ b/manifest.ttl
@@ -0,0 +1,110 @@
+# LV2 Plugin
+# Copyright 2016 Bent Bisballe Nyeng <deva@aasimon.org>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix pprops: <http://lv2plug.in/ns/ext/port-props#> .
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://example.org/plugintest#ui>
+ a ui:X11UI ;
+ lv2:requiredFeature ui:resize ;
+ lv2:extensionData ui:resize ;
+ lv2:requiredFeature ui:idleInterface ;
+ lv2:extensionData ui:idleInterface ;
+ ui:binary <plugintest_lv2.so> .
+
+<http://example.org/plugintest>
+ a lv2:Plugin ;
+ lv2:binary <plugintest_lv2.so> ;
+ a lv2:InstrumentPlugin ;
+ doap:name "LV2Test" ;
+ ui:ui <http://example.org/plugintest#ui> ;
+ doap:maintainer [
+ foaf:name "LV2Test" ;
+ foaf:homepage <http://www.example.org/plugintest> ;
+ ] ;
+ doap:license <http://usefulinc.com/doap/licenses/gpl> ;
+ doap:license <http://opensource.org/licenses/gpl-3.0> ;
+ lv2:optionalFeature <http://lv2plug.in/ns/ext/uri-map> ;
+ lv2:optionalFeature <http://lv2plug.in/ns/ext/event> ;
+ lv2:extensionData state:interface ;
+ lv2:port [
+ a lv2:InputPort, lv2:ControlPort ;
+ lv2:index 0 ;
+ lv2:symbol "lv2_freewheel" ;
+ lv2:name "Freewheel" ;
+ lv2:default 0.0 ;
+ lv2:minimum 0.0 ;
+ lv2:maximum 1.0 ;
+ lv2:designation <http://lv2plug.in/ns/lv2core#freeWheeling> ;
+ lv2:portProperty lv2:toggled ;
+ lv2:portProperty pprops:hasStrictBounds;
+ ] , [
+ a lv2:OutputPort, lv2:ControlPort ;
+ lv2:designation <http://lv2plug.in/ns/lv2core#latency>;
+ lv2:index 1;
+ lv2:symbol "latency";
+ lv2:name "Latency";
+ lv2:minimum 0;
+ lv2:maximum 192000;
+ lv2:portProperty lv2:reportsLatency, lv2:integer;
+ ] , [
+ a atom:AtomPort ,
+ lv2:InputPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ;
+ lv2:index 2 ;
+ lv2:symbol "control" ;
+ lv2:name "Control"
+ ] , [
+ a atom:AtomPort ,
+ lv2:OutputPort;
+ atom:bufferType atom:Sequence ;
+ atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ;
+ lv2:index 3 ;
+ lv2:symbol "control" ;
+ lv2:name "Control"
+ ] , [
+ a lv2:AudioPort ,
+ lv2:InputPort ;
+ lv2:index 4 ;
+ lv2:symbol "in1" ;
+ lv2:name "In1"
+ ] , [
+ a lv2:AudioPort ,
+ lv2:InputPort ;
+ lv2:index 5 ;
+ lv2:symbol "in2" ;
+ lv2:name "In2"
+ ] , [
+ a lv2:AudioPort ,
+ lv2:OutputPort ;
+ lv2:index 6 ;
+ lv2:symbol "out1" ;
+ lv2:name "Out1"
+ ], [
+ a lv2:AudioPort ,
+ lv2:OutputPort ;
+ lv2:index 7 ;
+ lv2:symbol "out2" ;
+ lv2:name "Out2"
+ ] .
diff --git a/midievent.cc b/midievent.cc
new file mode 100644
index 0000000..9b3dfb5
--- /dev/null
+++ b/midievent.cc
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * midievent.cc
+ *
+ * Sun Feb 7 15:09:01 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "midievent.h"
+
+#include <iostream>
+
+MidiEvent::MidiEvent(int64_t time, const char* data, std::size_t size)
+ : time(time)
+{
+ std::cout << __PRETTY_FUNCTION__ <<
+ " data: " << (void*)data <<
+ " size: " << size <<
+ std::endl;
+
+ this->data.resize(size);
+ for(std::size_t i = 0; i < size; ++i)
+ {
+ this->data[i] = data[i];
+ }
+
+ if ((data[0] & 0xF0) == 0x80) { // note off
+ type = MidiEventType::NoteOff;
+ key = data[1];
+ velocity = data[2];
+ }
+
+ if ((data[0] & 0xF0) == 0x90) { // note on
+ type = MidiEventType::NoteOn;
+ key = data[1];
+ velocity = data[2];
+ }
+}
+
+int64_t MidiEvent::getTime() const
+{
+ return time;
+}
+
+const char* MidiEvent::getData() const
+{
+ return data.data();
+}
+
+std::size_t MidiEvent::getSize() const
+{
+ return data.size();
+}
diff --git a/midievent.h b/midievent.h
new file mode 100644
index 0000000..046a01a
--- /dev/null
+++ b/midievent.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * midievent.h
+ *
+ * Sun Feb 7 15:09:01 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <vector>
+
+enum class MidiEventType {
+ Unknown,
+ NoteOn,
+ NoteOff,
+};
+
+class MidiEvent {
+public:
+ MidiEvent() = default;
+ MidiEvent(int64_t time, const char* data, std::size_t size);
+
+ int64_t getTime() const;
+ const char* getData() const;
+ std::size_t getSize() const;
+
+ MidiEventType type{MidiEventType::Unknown};
+ int key{0};
+ int velocity{0};
+
+private:
+ int64_t time;
+ std::vector<char> data;
+};
diff --git a/plugin.h b/plugin.h
new file mode 100644
index 0000000..0c58771
--- /dev/null
+++ b/plugin.h
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * plugin.h
+ *
+ * Sun Feb 7 14:11:40 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include <cstdlib>
+
+class MidiEvent;
+
+//! Abstract base-class for plugin implementations.
+class Plugin {
+public:
+ //! Implement this to create a new plugin instance.
+ static Plugin* create();
+
+ //! Get current free-wheel mode.
+ virtual bool getFreeWheel() const = 0;
+
+ //! This method is called by the host when the free-wheel mode changes.
+ virtual void onFreeWheelChange(bool freewheel) = 0;
+
+
+ //! Call this to get current samplerate.
+ virtual float getSamplerate() = 0;
+
+ //! This method is called by the host when the samplerate changes.
+ virtual void onSamplerateChange(float samplerate) = 0;
+
+
+ //! Call this to get current frame-size.
+ virtual std::size_t getFramesize() = 0;
+
+ //! This method is called by the host when the frame-size changes.
+ virtual void onFramesizeChange(std::size_t framesize) = 0;
+
+
+ //! Call this to get current active state
+ virtual bool getActive() = 0;
+
+ //! This method is called by the host when the active state changes.
+ virtual void onActiveChange(bool active) = 0;
+
+
+ //! This method is called by the host to get the current state for storing.
+ virtual std::string onStateSave() = 0;
+
+ //! This method is called by the host when a new state has been loaded.
+ virtual void onStateRestore(const std::string& config) = 0;
+
+
+ //! This is method is called by the host to get the current latency.
+ //! \param The latency in samples.
+ virtual float getLatency() = 0;
+
+ //! Call this method to signal a latency change to the host.
+ //! \param latency The latency in samples.
+ virtual void setLatency(float latency) = 0;
+
+
+ //! Called by the the host to get the number of midi input channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfMidiInputs() = 0;
+
+ //! Called by the the host to get the number of midi output channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfMidiOutputs() = 0;
+
+ //! Called by the the host to get the number of audio input channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfAudioInputs() = 0;
+
+ //! Called by the the host to get the number of audio output channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfAudioOutputs() = 0;
+
+
+ //! Get unique plugin id.
+ virtual std::string getId() = 0;
+
+ //! Process callback.
+ virtual void process(std::size_t pos,
+ const std::vector<MidiEvent>& input_events,
+ std::vector<MidiEvent>& output_events,
+ const std::vector<const float*>& input_samples,
+ const std::vector<float*>& output_samples,
+ std::size_t count) = 0;
+
+
+ //
+ // GUI (optional)
+ //
+
+ //! Return true if a GUI implementation is to be used.
+ virtual bool hasGUI()
+ {
+ return false;
+ }
+
+ //! Create new window.
+ virtual void createWindow(void *parent) {}
+
+ //! Destroy window.
+ virtual void onDestroyWindow() {}
+
+ //! Show window.
+ virtual void onShowWindow() {}
+
+ //! Hide window.
+ virtual void onHideWindow() {}
+
+ //! Called regularly by host; process ui events.
+ virtual void onIdle() {}
+
+ //! Signal new window size to host.
+ virtual void resizeWindow(std::size_t width, std::size_t height) = 0;
+
+ //! Signal close window event to the host.
+ virtual void closeWindow() = 0;
+};
diff --git a/pluginlv2.cc b/pluginlv2.cc
new file mode 100644
index 0000000..d351104
--- /dev/null
+++ b/pluginlv2.cc
@@ -0,0 +1,637 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pluginlv2.cc
+ *
+ * Sun Feb 7 15:15:24 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "pluginlv2.h"
+
+#include <iostream>
+#include <assert.h>
+
+#include "midievent.h"
+
+#define LV2_PLUGIN_URI__atom LV2_PLUGIN_URI "/atom#"
+#define LV2_PLUGIN_URI__instance LV2_PLUGIN_URI "#plugin-instance"
+#define LV2_PLUGIN_URI__ui LV2_PLUGIN_URI "#ui"
+
+#include "lv2/lv2plug.in/ns/ext/atom/util.h"
+#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
+#include <lv2/lv2plug.in/ns/ext/instance-access/instance-access.h>
+
+bool PluginLV2::getFreeWheel() const
+{
+ return free_wheel;
+}
+
+float PluginLV2::getSamplerate()
+{
+ return sample_rate;
+}
+
+std::size_t PluginLV2::getFramesize()
+{
+ return frame_size;
+}
+
+bool PluginLV2::getActive()
+{
+ return active;
+}
+
+float PluginLV2::getLatency()
+{
+ if(latency_port)
+ {
+ return *latency_port;
+ }
+
+ return 0.0f;
+}
+
+void PluginLV2::setLatency(float latency)
+{
+ if(latency_port)
+ {
+ *latency_port = latency;
+ }
+}
+
+LV2_Handle PluginLV2::instantiate(const struct _LV2_Descriptor* descriptor,
+ double sample_rate,
+ const char* bundle_path,
+ const LV2_Feature *const *features)
+{
+ PluginLV2* plugin_lv2 = createEffectInstance();
+
+ plugin_lv2->sample_rate = sample_rate;
+
+ plugin_lv2->input_event_ports.resize(plugin_lv2->getNumberOfMidiInputs(),
+ nullptr);
+ plugin_lv2->output_event_ports.resize(plugin_lv2->getNumberOfMidiOutputs(),
+ nullptr);
+
+ plugin_lv2->input_audio_ports.resize(plugin_lv2->getNumberOfAudioInputs());
+ plugin_lv2->output_audio_ports.resize(plugin_lv2->getNumberOfAudioOutputs());
+
+ for(auto& port : plugin_lv2->output_audio_ports)
+ {
+ port = nullptr;
+ }
+
+ for(auto& port : plugin_lv2->input_audio_ports)
+ {
+ port = nullptr;
+ }
+
+ while(*features != nullptr)
+ {
+ std::string uri = (*features)->URI;
+ void* data = (*features)->data;
+
+ if(uri == LV2_URID__map)
+ {
+ plugin_lv2->map = (LV2_URID_Map*)data;
+ }
+
+ ++features;
+ }
+
+ // Only reported on creation.
+ plugin_lv2->onSamplerateChange(sample_rate);
+
+ return (LV2_Handle)plugin_lv2;
+}
+
+void PluginLV2::connectPort(LV2_Handle instance, uint32_t port,
+ void *data_location)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+
+ if(port == static_cast<uint32_t>(LV2Ports::FreeWheel))
+ {
+ plugin_lv2->free_wheel_port = (float*)data_location;
+ if(plugin_lv2->free_wheel_port)
+ {
+ plugin_lv2->free_wheel = (*plugin_lv2->free_wheel_port != 0.0f);
+ // Signal first time.
+ plugin_lv2->onFreeWheelChange(plugin_lv2->free_wheel);
+ }
+ }
+
+ if(port == static_cast<uint32_t>(LV2Ports::Latency))
+ {
+ plugin_lv2->latency_port = (float*)data_location;
+ }
+
+ uint32_t port_offset = static_cast<uint32_t>(LV2Ports::PortOffset);
+
+ if((port >= port_offset) &&
+ (port < (port_offset + plugin_lv2->getNumberOfMidiInputs())))
+ {
+ int port_index = port - port_offset;
+ plugin_lv2->input_event_ports[port_index] =
+ (LV2_Atom_Sequence*)data_location;
+ }
+
+ port_offset += plugin_lv2->getNumberOfMidiInputs();
+
+ if((port >= port_offset) &&
+ (port < (port_offset + plugin_lv2->getNumberOfMidiOutputs())))
+ {
+ int port_index = port - port_offset;
+ plugin_lv2->output_event_ports[port_index] =
+ (LV2_Atom_Sequence*)data_location;
+ }
+
+ port_offset += plugin_lv2->getNumberOfMidiOutputs();
+
+ if((port >= port_offset) &&
+ (port < (port_offset + plugin_lv2->getNumberOfAudioInputs())))
+ {
+ int port_index = port - port_offset;
+ plugin_lv2->input_audio_ports[port_index] = (float*)data_location;
+ }
+
+ port_offset += plugin_lv2->getNumberOfAudioInputs();
+
+ if((port >= port_offset) &&
+ (port < (port_offset + plugin_lv2->getNumberOfAudioOutputs())))
+ {
+ int port_index = port - port_offset;
+ plugin_lv2->output_audio_ports[port_index] = (float*)data_location;
+ }
+}
+
+class Sequence {
+public:
+ Sequence(LV2_URID_Map& map, void* buffer, std::size_t buffer_size);
+ void clear();
+ void addMidiEvent(std::size_t pos, const char* data, std::size_t size);
+ void* data();
+
+private:
+ void *buffer;
+ std::size_t buffer_size;
+ LV2_Atom_Sequence *seq;
+ LV2_URID_Map& map;
+};
+
+Sequence::Sequence(LV2_URID_Map& map, void* buffer, std::size_t buffer_size)
+ : map(map)
+{
+ this->buffer = buffer;
+ this->buffer_size = buffer_size;
+
+ seq = (LV2_Atom_Sequence*)buffer;
+
+ seq->atom.size = sizeof(LV2_Atom_Sequence_Body);
+ seq->atom.type = map.map(map.handle, LV2_ATOM__Sequence);
+ seq->body.unit = 0;
+ seq->body.pad = 0;
+}
+
+// Keep this to support atom extension from lv2 < 1.10
+static inline void _lv2_atom_sequence_clear(LV2_Atom_Sequence* seq)
+{
+ seq->atom.size = sizeof(LV2_Atom_Sequence_Body);
+}
+
+void Sequence::clear()
+{
+ _lv2_atom_sequence_clear(seq);
+}
+
+// Keep this to support atom extension from lv2 < 1.10
+static inline LV2_Atom_Event*
+_lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq,
+ uint32_t capacity,
+ const LV2_Atom_Event* event)
+{
+ const uint32_t total_size = (uint32_t)sizeof(*event) + event->body.size;
+
+ if(capacity - seq->atom.size < total_size)
+ {
+ return nullptr;
+ }
+
+ LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size);
+ memcpy(e, event, total_size);
+
+ seq->atom.size += lv2_atom_pad_size(total_size);
+
+ return e;
+}
+
+void Sequence::addMidiEvent(std::size_t pos, const char* data, std::size_t size)
+{
+ typedef struct {
+ LV2_Atom_Event event;
+ uint8_t msg[6];
+ } MIDINoteEvent;
+
+
+ MIDINoteEvent ev;
+ ev.event.time.frames = pos;
+ ev.event.body.type = map.map(map.handle, LV2_MIDI__MidiEvent);
+ ev.event.body.size = size;//sizeof(LV2_Atom_Event) + size;
+
+ assert(size <= sizeof(ev.msg)); // Assert that we have room for the message
+
+ memcpy(ev.msg, data, size + sizeof(LV2_Atom_Event));
+
+ _lv2_atom_sequence_append_event(seq, this->buffer_size, &ev.event);
+}
+
+void* Sequence::data()
+{
+ return buffer;
+}
+
+void PluginLV2::run(LV2_Handle instance, uint32_t sample_count)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+
+ // Handle free-wheel state
+ if(plugin_lv2->free_wheel_port != nullptr)
+ {
+ bool new_free_wheel = *plugin_lv2->free_wheel_port != 0.0f;
+ if(new_free_wheel != plugin_lv2->free_wheel)
+ {
+ plugin_lv2->free_wheel = new_free_wheel;
+ plugin_lv2->onFreeWheelChange(plugin_lv2->free_wheel);
+ }
+ }
+
+ // Handle frame size
+ if(plugin_lv2->frame_size != sample_count)
+ {
+ plugin_lv2->frame_size = sample_count;
+ plugin_lv2->onFramesizeChange(plugin_lv2->frame_size);
+ }
+
+ // Convert input lv2 events to input events.
+ std::vector<MidiEvent> input_events;
+ for(std::size_t port = 0; port < plugin_lv2->getNumberOfMidiInputs(); ++port)
+ {
+ if(plugin_lv2->input_event_ports[port] == nullptr)
+ {
+ continue; // Not yet connected.
+ }
+
+ auto& event_port = plugin_lv2->input_event_ports[port];
+
+ LV2_Atom_Event* ev = lv2_atom_sequence_begin(&event_port->body);
+ while(!lv2_atom_sequence_is_end(&event_port->body,
+ event_port->atom.size,
+ ev))
+ {
+ if(ev->body.type != plugin_lv2->map->map(plugin_lv2->map->handle,
+ LV2_MIDI__MidiEvent))
+ {
+ continue; // not a midi event.
+ }
+
+ const char* data = (char*)(ev + 1);
+ input_events.emplace_back(ev->time.frames, data, ev->body.size);
+
+ ev = lv2_atom_sequence_next(ev);
+ }
+ }
+
+ std::vector<MidiEvent> output_events;
+
+ // Process events and audio
+ plugin_lv2->process(plugin_lv2->pos,
+ input_events,
+ output_events,
+ plugin_lv2->input_audio_ports,
+ plugin_lv2->output_audio_ports,
+ sample_count);
+
+ // Convert output events to lv2 events
+ if(plugin_lv2->getNumberOfMidiOutputs() > 0)
+ {
+ if(plugin_lv2->map != nullptr)
+ {
+ if(plugin_lv2->output_event_ports[0] != nullptr) // Not yet connected?
+ {
+ auto& event_port = plugin_lv2->output_event_ports[0]; // TODO: Split?
+ Sequence seq(*plugin_lv2->map,
+ &event_port->body + 1,
+ event_port->atom.size);
+ for(auto midi_event : output_events)
+ {
+ seq.addMidiEvent(midi_event.getTime(),
+ midi_event.getData(),
+ midi_event.getSize());
+ }
+ }
+ }
+ }
+
+ plugin_lv2->pos += sample_count;
+}
+
+void PluginLV2::activate(LV2_Handle instance)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+ plugin_lv2->active = true;
+ plugin_lv2->onActiveChange(plugin_lv2->active);
+}
+
+void PluginLV2::deactivate(LV2_Handle instance)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+ plugin_lv2->active = false;
+ plugin_lv2->onActiveChange(plugin_lv2->active);
+}
+
+void PluginLV2::cleanup(LV2_Handle instance)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+ delete plugin_lv2;
+}
+
+//
+// State handling
+//
+
+LV2_State_Status PluginLV2::save(LV2_Handle instance,
+ LV2_State_Store_Function store,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature *const * features)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+
+ if(plugin_lv2->map == nullptr)
+ {
+ // Missing urid feature?
+ return LV2_STATE_ERR_NO_FEATURE;
+ }
+
+ std::string config = plugin_lv2->onStateSave();
+
+ store(handle,
+ plugin_lv2->map->map(plugin_lv2->map->handle,
+ LV2_PLUGIN_URI__atom "config"),
+ config.data(),
+ config.length(),
+ plugin_lv2->map->map(plugin_lv2->map->handle, LV2_ATOM__Chunk),
+ LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
+
+ return LV2_STATE_SUCCESS;
+}
+
+LV2_State_Status PluginLV2::restore(LV2_Handle instance,
+ LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature *const * features)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+
+ if(plugin_lv2->map == nullptr)
+ {
+ // Missing urid feature?
+ return LV2_STATE_ERR_NO_FEATURE;
+ }
+
+ std::size_t size;
+ uint32_t type;
+
+ const char* data =
+ (const char*)retrieve(handle,
+ plugin_lv2->map->map(plugin_lv2->map->handle,
+ LV2_PLUGIN_URI__atom "config"),
+ &size, &type, &flags);
+
+ if(data && size)
+ {
+ std::string config;
+
+ config.append(data, size);
+ plugin_lv2->onStateRestore(config);
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static LV2_State_Interface persist = {
+ PluginLV2::save,
+ PluginLV2::restore
+};
+
+const void* PluginLV2::extensionData(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ {
+ return &persist;
+ }
+
+ return nullptr;
+}
+
+
+//
+// GUI
+//
+LV2UI_Handle PluginLV2::uiInstantiate(const struct _LV2UI_Descriptor*descriptor,
+ const char * plugin_uri,
+ const char * bundle_path,
+ LV2UI_Write_Function write_function,
+ LV2UI_Controller controller,
+ LV2UI_Widget* widget,
+ const LV2_Feature * const * features)
+{
+ LV2_Handle instance = nullptr;
+ void* parent = nullptr;
+ LV2UI_Resize* resize = nullptr;
+
+ while(*features != nullptr) {
+ std::string uri = (*features)->URI;
+ void *data = (*features)->data;
+
+ if(uri == LV2_INSTANCE_ACCESS_URI)
+ {
+ instance = (LV2_Handle)data;
+ }
+
+ if(uri == LV2_UI__parent)
+ {
+ parent = data;
+ }
+
+ if(uri == LV2_UI__resize)
+ {
+ resize = (LV2UI_Resize*)data;
+ }
+
+ features++;
+ }
+
+ if(instance == nullptr)
+ {
+ return nullptr;
+ }
+
+ PluginLV2* plugin_lv2 = (PluginLV2*)instance;
+
+ // Do we have a GUI?
+ if(plugin_lv2->hasGUI() == false)
+ {
+ return nullptr;
+ }
+
+ plugin_lv2->resize = resize;
+
+ plugin_lv2->createWindow(parent);
+ plugin_lv2->resize->ui_resize(plugin_lv2->resize->handle, 100, 100);
+
+ return plugin_lv2;
+}
+
+void PluginLV2::resizeWindow(std::size_t width, std::size_t height)
+{
+ if(resize)
+ {
+ resize->ui_resize(resize->handle, width, height);
+ }
+}
+
+void PluginLV2::closeWindow()
+{
+}
+
+static const LV2_Descriptor descriptor = {
+ LV2_PLUGIN_URI,
+ PluginLV2::instantiate,
+ PluginLV2::connectPort,
+ PluginLV2::activate,
+ PluginLV2::run,
+ PluginLV2::deactivate,
+ PluginLV2::cleanup,
+ PluginLV2::extensionData
+};
+
+void PluginLV2::uiCleanup(LV2UI_Handle handle)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)handle;
+ plugin_lv2->onDestroyWindow () ;
+
+}
+
+int PluginLV2::uiIdle(LV2UI_Handle handle)
+{
+ PluginLV2* plugin_lv2 = (PluginLV2*)handle;
+ plugin_lv2->onIdle();
+ return 0;
+}
+
+static const LV2UI_Idle_Interface idle_iface = {
+ PluginLV2::uiIdle
+};
+
+const void* PluginLV2::uiExtensionData(const char* uri)
+{
+ if(!strcmp(uri, LV2_UI__idleInterface))
+ {
+ return &idle_iface;
+ }
+
+ return NULL;
+}
+
+static LV2UI_Descriptor ui_descriptor = {
+ LV2_PLUGIN_URI__ui,
+ PluginLV2::uiInstantiate,
+ PluginLV2::uiCleanup,
+ nullptr,//PluginLV2::ui_port_event,
+ PluginLV2::uiExtensionData
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor* lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0:
+ return &descriptor;
+ default:
+ return nullptr;
+ }
+}
+
+LV2_SYMBOL_EXPORT
+const LV2UI_Descriptor *lv2ui_descriptor(uint32_t index)
+{
+ switch(index) {
+ case 0:
+ return &ui_descriptor;
+ default:
+ return nullptr;
+ }
+}
+
+//
+// DynManifest experiments failed, but the code is kept here for nostalgic
+// reasons :-)
+//
+//
+//int lv2_dyn_manifest_open(LV2_Dyn_Manifest_Handle* handle,
+// const LV2_Feature* const* features)
+//{
+// *handle = createEffectInstance();
+// return 0;
+//}
+//
+//int lv2_dyn_manifest_get_subjects(LV2_Dyn_Manifest_Handle handle, FILE* fp)
+//{
+//// PluginLV2* plugin_lv2 = (PluginLV2*)handle;
+//
+// fprintf(fp, "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n");
+// fprintf(fp, "<" LV2_PLUGIN_URI "> a lv2:Plugin .\n");
+// return 0;
+//}
+//
+//int lv2_dyn_manifest_get_data(LV2_Dyn_Manifest_Handle handle, FILE* fp,
+// const char* uri)
+//{
+// //PluginLV2* plugin_lv2 = (PluginLV2*)handle;
+// printf("%s '%s'\n", __PRETTY_FUNCTION__, uri);
+// return 0;
+//}
+//
+//void lv2_dyn_manifest_close(LV2_Dyn_Manifest_Handle handle)
+//{
+// PluginLV2* plugin_lv2 = (PluginLV2*)handle;
+// delete plugin_lv2;
+//}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/pluginlv2.h b/pluginlv2.h
new file mode 100644
index 0000000..61fef02
--- /dev/null
+++ b/pluginlv2.h
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pluginlv2.h
+ *
+ * Sun Feb 7 15:15:23 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <plugin.h>
+
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/state/state.h>
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/dynmanifest/dynmanifest.h>
+#include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
+
+enum class LV2Ports
+{
+ FreeWheel = 0,
+ Latency = 1,
+ PortOffset = 2,
+};
+
+class PluginLV2
+ : public Plugin
+{
+public:
+ virtual ~PluginLV2() = default;
+
+ //! Get current free-wheel mode.
+ bool getFreeWheel() const override;
+
+ //! This method is called by the host when the free-wheel mode changes.
+ virtual void onFreeWheelChange(bool freewheel) override = 0;
+
+
+ //! Call this to get current samplerate.
+ float getSamplerate() override;
+
+ //! This method is called by the host when the samplerate changes.
+ virtual void onSamplerateChange(float samplerate) = 0;
+
+ //! Call this to get current frame-size.
+ std::size_t getFramesize() override;
+
+ //! This method is called by the host when the frame-size changes.
+ virtual void onFramesizeChange(std::size_t framesize) override = 0;
+
+
+ //! Call this to get current active state
+ bool getActive() override;
+
+ //! This method is called by the host when the active state changes.
+ virtual void onActiveChange(bool active) override = 0;
+
+
+ //! This method is called by the host to get the current state for storing.
+ virtual std::string onStateSave() override = 0;
+
+ //! This method is called by the host when a new state has been loaded.
+ virtual void onStateRestore(const std::string& config) override = 0;
+
+
+ //! This is method is called by the host to get the current latency.
+ //! \param The latency in samples.
+ float getLatency() override;
+
+ //! Call this method to signal a latency change to the host.
+ //! \param latency The latency in samples.
+ void setLatency(float latency) override;
+
+
+ //! Called by the the host to get the number of audio input channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfAudioInputs() override = 0;
+
+ //! Called by the the host to get the number of audio output channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfAudioOutputs() override = 0;
+
+
+ //! Called by the the host to get the number of midi input channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfMidiInputs() override = 0;
+
+ //! Called by the the host to get the number of midi output channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfMidiOutputs() override = 0;
+
+
+ //! Get unique plugin id.
+ std::string getId() override = 0;
+
+ virtual void process(std::size_t pos,
+ const std::vector<MidiEvent>& input_events,
+ std::vector<MidiEvent>& output_events,
+ const std::vector<const float*>& input_samples,
+ const std::vector<float*>& output_samples,
+ std::size_t count) = 0;
+
+ //
+ // GUI (optional)
+ //
+
+ //! Return true if a GUI implementation is to be used.
+ virtual bool hasGUI() override
+ {
+ return false;
+ }
+
+ //! Create new window.
+ virtual void createWindow(void *parent) override {}
+
+ //! Destroy window.
+ virtual void onDestroyWindow() override {}
+
+ //! Show window.
+ virtual void onShowWindow() override {}
+
+ //! Hide window.
+ virtual void onHideWindow() override {}
+
+ //! Called regularly by host; process ui events.
+ virtual void onIdle() override {}
+
+ //! Signal new window size to host.
+ void resizeWindow(std::size_t width, std::size_t height) override;
+
+ //! Signal close window event to the host.
+ void closeWindow() override;
+
+public:
+ static LV2_Handle instantiate(const struct _LV2_Descriptor* descriptor,
+ double sample_rate,
+ const char* bundle_path,
+ const LV2_Feature* const * features);
+
+ static void connectPort(LV2_Handle instance, uint32_t port,
+ void *data_location);
+
+ static void run(LV2_Handle instance, uint32_t sample_count);
+
+ static void activate(LV2_Handle instance);
+
+ static void deactivate(LV2_Handle instance);
+
+ static void cleanup(LV2_Handle instance);
+
+ static const void* extensionData(const char *uri);
+
+ static LV2_State_Status save(LV2_Handle instance,
+ LV2_State_Store_Function store,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature *const * features);
+
+ static LV2_State_Status restore(LV2_Handle instance,
+ LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature *const * features);
+
+ static LV2UI_Handle uiInstantiate(const struct _LV2UI_Descriptor * descriptor,
+ const char * plugin_uri,
+ const char * bundle_path,
+ LV2UI_Write_Function write_function,
+ LV2UI_Controller controller,
+ LV2UI_Widget * widget,
+ const LV2_Feature * const * features);
+
+ static void uiCleanup(LV2UI_Handle handle);
+
+ static int uiIdle(LV2UI_Handle handle);
+
+ static const void* uiExtensionData(const char* uri);
+
+private:
+ float* free_wheel_port{nullptr};
+ bool free_wheel{false};
+
+ float sample_rate{0};
+
+ float* latency_port{nullptr};
+
+ std::size_t frame_size{0};
+
+ std::size_t pos{0};
+
+ std::vector<LV2_Atom_Sequence*> input_event_ports;
+ std::vector<LV2_Atom_Sequence*> output_event_ports;
+ std::vector<const float*> input_audio_ports;
+ std::vector<float*> output_audio_ports;
+
+ LV2_URID_Map* map{nullptr};
+
+ bool active{false};
+
+ //
+ // GUI
+ //
+ LV2UI_Resize* resize{nullptr};
+};
+
+PluginLV2* createEffectInstance();
diff --git a/plugintest.cc b/plugintest.cc
new file mode 100644
index 0000000..93debb2
--- /dev/null
+++ b/plugintest.cc
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pluginlv2test.cc
+ *
+ * Mon Feb 8 07:21:09 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "plugintest.h"
+
+#include "midievent.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <iostream>
+
+#ifdef LV2
+// Entry point for lv2 plugin instantiation.
+PluginLV2* createEffectInstance()
+{
+ return new PluginTest();
+}
+#endif
+
+#ifdef VST
+// Entry point for vst plugin instantiation.
+AudioEffect* createEffectInstance(audioMasterCallback audioMaster)
+{
+ return new PluginTest(audioMaster);
+}
+
+PluginTest::PluginTest(audioMasterCallback audioMaster)
+ : PluginVST(audioMaster)
+{
+}
+#endif
+
+void PluginTest::onFreeWheelChange(bool freewheel)
+{
+}
+
+void PluginTest::onSamplerateChange(float samplerate)
+{
+}
+
+void PluginTest::onFramesizeChange(size_t framesize)
+{
+}
+
+void PluginTest::onActiveChange(bool active)
+{
+}
+
+std::string PluginTest::onStateSave()
+{
+ return "";
+}
+
+void PluginTest::onStateRestore(const std::string& config)
+{
+}
+
+size_t PluginTest::getNumberOfMidiInputs()
+{
+ return 1;
+}
+
+size_t PluginTest::getNumberOfMidiOutputs()
+{
+ return 1;
+}
+
+size_t PluginTest::getNumberOfAudioInputs()
+{
+ return 2;
+}
+
+size_t PluginTest::getNumberOfAudioOutputs()
+{
+ return 2;
+}
+
+std::string PluginTest::getId()
+{
+ return "PluginTest";
+}
+
+void PluginTest::process(size_t pos,
+ const std::vector<MidiEvent>& input_events,
+ std::vector<MidiEvent>& output_events,
+ const std::vector<const float*>& input_samples,
+ const std::vector<float*>& output_samples,
+ size_t count)
+{
+ setLatency(44100);
+
+ float sample_rate = getSamplerate();
+ static float frequency = 0.0f;
+ static float amplitude = 0.0f;
+
+ for(auto& event : input_events)
+ {
+ if(event.type == MidiEventType::NoteOn)
+ {
+ float note = event.key;
+ frequency = 110.0f * pow(2, note/12.0);
+ amplitude = (float)event.velocity / 127.0f;
+ }
+
+ if(event.type == MidiEventType::NoteOff)
+ {
+ frequency = 0.0f;
+ amplitude = 0.0f;
+ }
+ }
+
+ for(float* ch : output_samples)
+ {
+ float p = pos;
+ for(size_t s = 0; s < count; ++s)
+ {
+ const float pi = 3.141592653f;
+ float value = sin(p * 2.0f * pi / sample_rate * frequency) * amplitude;
+
+ //value = value * 10.0;
+
+ ch[s] = value;
+
+ p += 1.0f;
+ }
+ }
+}
+
+bool PluginTest::hasGUI()
+{
+ return true;
+}
+
+static int n = 0;
+static const char message[] = "Hello, world!";
+
+#if defined(WIN32)
+LRESULT CALLBACK PluginTest::wndProc(HWND hwnd, UINT iMsg,
+ WPARAM wParam, LPARAM lParam)
+{
+ auto self = (PluginTest*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ PAINTSTRUCT ps;
+ HDC hdc;
+
+ // Switch according to what type of message we have received
+
+ switch(iMsg) {
+ case WM_PAINT:
+ // We receive WM_PAINT every time window is updated
+ hdc = BeginPaint(hwnd, &ps);
+ TextOut(hdc, 10, n % 100, message, 13);
+ ++n;
+ Rectangle(hdc, self->mouseX, self->mouseY,
+ self->mouseX + 10, self->mouseY + 10);
+ EndPaint(hwnd, &ps);
+ break;
+
+ case WM_MOUSEMOVE:
+ self->mouseX = (short)LOWORD(lParam);
+ self->mouseY = (short)HIWORD(lParam);
+ break;
+ }
+
+ // Send any messages we don't handle to default window procedure
+ return DefWindowProc(hwnd, iMsg, wParam, lParam);
+}
+#endif // defined(WIN32)
+
+void PluginTest::createWindow(void *parent)
+{
+#if defined(X11)
+ xDisplay = XOpenDisplay(nullptr);
+
+ xScreen = DefaultScreen(xDisplay);
+
+ xWindow = XCreateSimpleWindow(xDisplay, (Window)parent, 10, 10, 100, 100, 1,
+ BlackPixel(xDisplay, xScreen),
+ WhitePixel(xDisplay, xScreen));
+
+ XSelectInput(xDisplay, xWindow,
+ ExposureMask | KeyPressMask | PointerMotionMask);
+#endif // defined(X11)
+
+#if defined(WIN32)
+ static char szAppName[] = "PluginTest";
+ WNDCLASSEX wndclass = {};
+
+ hInstance = GetModuleHandle(nullptr);
+
+ wndclass.cbSize = sizeof(wndclass);
+ wndclass.style = CS_HREDRAW | CS_VREDRAW;
+ wndclass.lpfnWndProc = PluginTest::wndProc;
+ wndclass.hInstance = hInstance;
+ wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
+ wndclass.lpszClassName = szAppName;
+ wndclass.lpszMenuName = NULL;
+ wndclass.cbWndExtra = sizeof(PluginTest*); // Size of data.
+
+ // Register a new window class with Windows
+ RegisterClassEx(&wndclass);
+
+ HWND hWndParent = (HWND)parent;
+
+ // Create a window based on our new class
+ hwnd = CreateWindow(szAppName, "PluginTest",
+ ((parent ? WS_CHILD : WS_OVERLAPPEDWINDOW) | WS_VISIBLE ),
+ 10, 10, 100, 100,
+ hWndParent, NULL, hInstance, NULL);
+
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
+
+#endif // defined(WIN32)
+
+ resizeWindow(100, 100);
+ onShowWindow();
+}
+
+void PluginTest::onDestroyWindow()
+{
+#if defined(X11)
+ XDestroyWindow(xDisplay, xWindow);
+ XCloseDisplay(xDisplay);
+#endif // defined(X11)
+
+#if defined(WIN32)
+ DestroyWindow(hwnd);
+// UnregisterClass(m_className, hInstance);
+#endif // defined(WIN32)
+}
+
+void PluginTest::onShowWindow()
+{
+#if defined(X11)
+ XMapWindow(xDisplay, xWindow);
+#endif // defined(X11)
+
+#if defined(WIN32)
+ // Show and update our window
+ ShowWindow(hwnd, SW_SHOW);
+ UpdateWindow(hwnd);
+#endif // defined(WIN32)
+}
+
+void PluginTest::onHideWindow()
+{
+#if defined(X11)
+ XUnmapWindow(xDisplay, xWindow);
+#endif // defined(X11)
+
+#if defined(WIN32)
+ ShowWindow(hwnd, SW_HIDE);
+#endif // defined(WIN32)
+
+}
+
+void PluginTest::onIdle()
+{
+#if defined(X11)
+ XEvent xEvent;
+ while(XPending(xDisplay) > 0)
+ {
+ XNextEvent(xDisplay, &xEvent);
+ switch(xEvent.type) {
+ case Expose:
+ break;
+
+ case MotionNotify:
+ mouseX = xEvent.xmotion.x;
+ mouseY = xEvent.xmotion.y;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ XClearWindow(xDisplay, xWindow);
+ XDrawRectangle(xDisplay, xWindow, DefaultGC(xDisplay, xScreen),
+ mouseX, mouseY, 10, 10);
+ XDrawString(xDisplay, xWindow, DefaultGC(xDisplay, xScreen),
+ 10, n % 100, message, strlen(message));
+#endif // defined(X11)
+
+#if defined(WIN32)
+ MSG msg;
+
+ while(PeekMessage(&msg, hwnd, 0, 0, PM_NOREMOVE))
+ {
+ GetMessage(&msg, hwnd, 0, 0);
+ TranslateMessage(&msg); // for certain keyboard messages
+ DispatchMessage(&msg); // send message to WndProc
+ }
+
+ InvalidateRect(hwnd, 0, TRUE);
+#endif // defined(WIN32)
+
+ ++n;
+}
+
+void PluginTest::resizeWindow(size_t width, size_t height)
+{
+}
+
+void PluginTest::closeWindow()
+{
+}
diff --git a/plugintest.h b/plugintest.h
new file mode 100644
index 0000000..2da5c73
--- /dev/null
+++ b/plugintest.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pluginlv2test.h
+ *
+ * Mon Feb 8 07:21:09 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#if defined(X11)
+#include <X11/Xlib.h>
+#endif // defined(X11)
+
+#if defined(WIN32)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif // defined(WIN32)
+
+#ifdef LV2
+#include <pluginlv2.h>
+#endif
+
+#ifdef VST
+#include <pluginvst.h>
+#endif
+
+class PluginTest
+#ifdef LV2
+ : public PluginLV2
+#endif
+#ifdef VST
+ : public PluginVST
+#endif
+{
+public:
+#ifdef VST
+ PluginTest(audioMasterCallback audioMaster);
+#endif
+
+ void onFreeWheelChange(bool freewheel) override;
+
+ void onFramesizeChange(size_t framesize) override;
+
+ void onSamplerateChange(float samplerate) override;
+
+ void onActiveChange(bool active) override;
+
+ std::string onStateSave() override;
+ void onStateRestore(const std::string& config) override;
+
+ size_t getNumberOfMidiInputs() override;
+ size_t getNumberOfMidiOutputs() override;
+ size_t getNumberOfAudioInputs() override;
+ size_t getNumberOfAudioOutputs() override;
+
+ std::string getId() override;
+
+ void process(size_t pos,
+ const std::vector<MidiEvent>& input_events,
+ std::vector<MidiEvent>& output_events,
+ const std::vector<const float*>& input_samples,
+ const std::vector<float*>& output_samples,
+ size_t count) override;
+
+ //
+ // GUI
+ //
+ bool hasGUI() override;
+ void createWindow(void *parent) override;
+ void onDestroyWindow() override;
+ void onShowWindow() override;
+ void onHideWindow() override;
+ void onIdle() override;
+ void resizeWindow(size_t width, size_t height) override;
+ void closeWindow() override;
+
+private:
+#if defined(X11)
+ Display* xDisplay{nullptr};
+ Window xWindow{0};
+ int xScreen{0};
+#endif // defined(X11)
+
+#if defined(WIN32)
+ HWND hwnd{0};
+ HMODULE hInstance{0};
+
+ static LRESULT CALLBACK wndProc(HWND hwnd, UINT iMsg,
+ WPARAM wParam, LPARAM lParam);
+#endif // defined(WIN32)
+
+ int mouseX{0};
+ int mouseY{0};
+};
diff --git a/pluginvst.cc b/pluginvst.cc
new file mode 100644
index 0000000..700a19d
--- /dev/null
+++ b/pluginvst.cc
@@ -0,0 +1,391 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pluginvst.cc
+ *
+ * Mon Feb 8 19:24:40 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "pluginvst.h"
+
+#include "midievent.h"
+
+#include <public.sdk/source/vst2.x/audioeffectx.h>
+#include <public.sdk/source/vst2.x/aeffeditor.h>
+
+bool PluginVST::getFreeWheel() const
+{
+ return free_wheel;
+}
+
+float PluginVST::getSamplerate()
+{
+ return AudioEffectX::getSampleRate();
+}
+
+
+std::size_t PluginVST::getFramesize()
+{
+ return AudioEffectX::getBlockSize();
+}
+
+bool PluginVST::getActive()
+{
+ return active;
+}
+
+float PluginVST::getLatency()
+{
+ return current_latency;
+}
+
+void PluginVST::setLatency(float latency)
+{
+ update_latency = latency;
+}
+
+void PluginVST::updateLatency()
+{
+ if(update_latency != current_latency)
+ {
+ AudioEffectX::setInitialDelay(current_latency);
+ AudioEffectX::ioChanged();
+ current_latency = update_latency;
+ }
+}
+
+//
+// VST AudioEffectX implementation:
+//
+PluginVST::PluginVST(audioMasterCallback audioMaster)
+ : AudioEffectX(audioMaster, 0, 0)
+{
+}
+
+PluginVST::~PluginVST()
+{
+}
+
+static uint32_t sdbm_hash(std::string input)
+{
+ unsigned long hash = 0;
+
+ for(auto& cha : input)
+ {
+ hash = (unsigned char)cha + (hash << 6) + (hash << 16) - hash;
+ }
+
+ return hash;
+}
+
+void PluginVST::open()
+{
+ // Called when plug-in is initialized.
+
+ // virtual void setUniqueID (VstInt32 iD)
+ // Must be called to set the plug-ins unique ID!
+ uint32_t hash = sdbm_hash(getId());
+ AudioEffect::setUniqueID(hash);
+
+ // virtual void setNumInputs (VstInt32 inputs)
+ // Set the number of inputs the plug-in will handle. For a plug-in which
+ // could change its IO configuration, this number is the maximun available
+ // inputs.
+ AudioEffect::setNumInputs(getNumberOfAudioInputs());
+
+ // virtual void setNumOutputs (VstInt32 outputs)
+ // Set the number of outputs the plug-in will handle. For a plug-in which
+ // could change its IO configuration, this number is the maximun available
+ // ouputs.
+ AudioEffect::setNumOutputs(getNumberOfAudioOutputs());
+
+ // virtual void canProcessReplacing (bool state=true)
+ // Tells that processReplacing() could be used. Mandatory in VST 2.4!
+ AudioEffect::canProcessReplacing(true);
+
+ // virtual void canDoubleReplacing (bool state=true)
+ // Tells that processDoubleReplacing() is implemented.
+ AudioEffect::canDoubleReplacing(false);
+
+ // virtual void programsAreChunks (bool state=true)
+ // Program data is handled in formatless chunks (using getChunk-setChunks).
+ AudioEffect::programsAreChunks(true); // for generic config string support.
+
+ // virtual void setInitialDelay (VstInt32 delay)
+ // Use to report the plug-in's latency (Group Delay).
+ AudioEffect::setInitialDelay(0);
+
+ AudioEffectX::isSynth(getNumberOfMidiInputs() > 0);
+
+ if(hasGUI())
+ {
+ editor = std::make_shared<UI>(*this);
+ setEditor(editor.get());
+ }
+}
+
+void PluginVST::close()
+{
+ // Called when plug-in will be released.
+ setEditor(nullptr);
+ editor = nullptr;
+}
+
+void PluginVST::suspend()
+{
+ // Called when plug-in is switched to off.
+ active = false;
+ onActiveChange(active);
+ updateLatency();
+}
+
+void PluginVST::resume()
+{
+ updateLatency();
+
+ // Called when plug-in is switched to on.
+ active = true;
+ onActiveChange(active);
+}
+
+void PluginVST::setSampleRate(float sampleRate)
+{
+ // Called when the sample rate changes (always in a suspend state).
+ onSamplerateChange(sampleRate);
+}
+
+void PluginVST::setBlockSize(VstInt32 blockSize)
+{
+ // Called when the Maximun block size changes (always in a suspend state).
+ // Note that the sampleFrames in Process Calls could be smaller than this
+ // block size, but NOT bigger.
+ onFramesizeChange(blockSize);
+}
+
+VstInt32 PluginVST::processEvents(VstEvents* events)
+{
+ // For each process cycle, processEvents() is called once before a
+ // processReplacing() call.
+
+ for(VstInt32 i = 0; i < events->numEvents; ++i)
+ {
+ auto event = events->events[i];
+ if(event->type != kVstMidiType)
+ {
+ continue;
+ }
+
+ auto midi_event = (VstMidiEvent*)event;
+ input_events.emplace_back(midi_event->deltaFrames,
+ midi_event->midiData,
+ midi_event->byteSize);
+ }
+
+ return 0;
+}
+
+void PluginVST::processReplacing(float** inputs, float** outputs,
+ VstInt32 sampleFrames)
+{
+ // Process 32 bit (single precision) floats (always in a resume state).
+
+ // 0 = realtime/normal
+ // 1 = non-realtime/rendering
+ // 2 = offline processing
+ long lvl = AudioEffectX::getCurrentProcessLevel();
+ bool last_free_wheel = free_wheel;
+ free_wheel = (lvl != 0);
+ if(last_free_wheel != free_wheel)
+ {
+ onFreeWheelChange(free_wheel);
+ }
+
+ std::vector<const float*> input_audio_ports;
+ for(std::size_t i = 0; i < getNumberOfAudioInputs(); ++i)
+ {
+ input_audio_ports.emplace_back(inputs[i]);
+ }
+
+ std::vector<float*> output_audio_ports;
+ output_audio_ports.resize(getNumberOfAudioOutputs());
+
+ for(std::size_t i = 0; i < getNumberOfAudioOutputs(); ++i)
+ {
+ output_audio_ports[i] = outputs[i];
+ }
+
+ std::vector<MidiEvent> output_events;
+
+ // Process events and audio
+ process(pos,
+ input_events,
+ output_events,
+ input_audio_ports,
+ output_audio_ports,
+ (std::size_t)sampleFrames);
+
+ input_events.clear();
+
+ if(getNumberOfMidiOutputs())
+ {
+ // Translate output_events to VST midi events.
+ std::vector<VstMidiEvent> vst_output_event_list;
+ vst_output_event_list.resize(output_events.size());
+ for(std::size_t i = 0; i < output_events.size(); ++i)
+ {
+ vst_output_event_list[i].deltaFrames = output_events[i].getTime();
+ vst_output_event_list[i].type = kVstMidiType;
+ const char* data = output_events[i].getData();
+ for(std::size_t j = 0; j < output_events[i].getSize(); ++j)
+ {
+ vst_output_event_list[i].midiData[j] = data[j];
+ }
+ vst_output_event_list[i].byteSize = output_events[i].getSize();
+ }
+
+ if(!vst_output_event_list.empty())
+ {
+ // Dispatch output events to host
+ VstEvents vst_output_events;
+ vst_output_events.numEvents = vst_output_event_list.size();
+ vst_output_events.events[0] = (VstEvent*)vst_output_event_list.data();
+ sendVstEventsToHost(&vst_output_events);
+ }
+ }
+
+ pos += sampleFrames;
+}
+
+VstInt32 PluginVST::getChunk(void **data, bool isPreset)
+{
+ std::string state = onStateSave();
+ char* chunk = (char*)malloc(state.size() + 1);
+ memcpy(chunk, state.data(), state.size());
+ *data = chunk;
+ return state.size();
+}
+
+VstInt32 PluginVST::setChunk(void *data, VstInt32 byteSize, bool isPreset)
+{
+ std::string state;
+ state.append((const char*)data, (std::size_t)byteSize);
+ onStateRestore(state);
+ return 0;
+}
+
+VstInt32 PluginVST::canDo(char* text)
+{
+ std::string feature = text;
+
+ // Midi input
+ if((feature == "receiveVstMidiEvent") &&//PlugCanDos::canDoReceiveVstMidiEvent
+ (getNumberOfMidiInputs() > 0))
+ {
+ return 1;
+ }
+
+ // Midi output
+ if((feature == "sendVstMidiEvent") && // PlugCanDos::canDoSendVstMidiEvent
+ (getNumberOfMidiOutputs() > 0))
+ {
+ return 1;
+ }
+
+ // For FreeWheel functionality.
+ if(feature == "offline") // PlugCanDos::canDoOffline
+ {
+ return 1;
+ }
+
+ // TODO: For soft-bypass state.
+ //if(feature == "bypass") // PlugCanDos::canDoBypass)
+ //{
+ // return 1;
+ //}
+
+ // TODO: For receiving metronome ticks?
+ //if(feature == "receiveVstTimeInfo") // PlugCanDos::canDoReceiveVstTimeInfo)
+ //{
+ // return 1;
+ //}
+
+ return 0;
+}
+
+void PluginVST::resizeWindow(std::size_t width, std::size_t height)
+{
+ if(editor)
+ {
+ editor->rect.top = 0;
+ editor->rect.left = 0;
+ editor->rect.right = width;
+ editor->rect.bottom = height;
+ }
+}
+
+void PluginVST::closeWindow()
+{
+}
+
+PluginVST::UI::UI(PluginVST& plugin_vst)
+ : AEffEditor(&plugin_vst)
+ , plugin_vst(plugin_vst)
+{
+}
+
+bool PluginVST::UI::open(void* ptr)
+{
+ plugin_vst.createWindow(ptr);
+
+ AEffEditor::open(ptr);
+
+ is_open = true;
+ return true;
+}
+
+void PluginVST::UI::close()
+{
+ is_open = false;
+ plugin_vst.onDestroyWindow();
+
+ AEffEditor::close();
+}
+
+bool PluginVST::UI::isOpen()
+{
+ return is_open;
+}
+
+void PluginVST::UI::idle()
+{
+ if(is_open)
+ {
+ plugin_vst.onIdle();
+ }
+
+ AEffEditor::idle();
+}
+
+bool PluginVST::UI::getRect(ERect** rect)
+{
+ *rect = &this->rect;
+ return true;
+}
diff --git a/pluginvst.h b/pluginvst.h
new file mode 100644
index 0000000..8e7334e
--- /dev/null
+++ b/pluginvst.h
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pluginvst.h
+ *
+ * Mon Feb 8 19:24:39 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of PluginGizmo.
+ *
+ * PluginGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PluginGizmo 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with PluginGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <memory>
+
+#include <plugin.h>
+
+#include <public.sdk/source/vst2.x/audioeffectx.h>
+#include <public.sdk/source/vst2.x/aeffeditor.h>
+
+#if defined(WIN32)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif // defined(WIN32)
+
+class PluginVST
+ : public Plugin
+ , public AudioEffectX
+{
+public:
+ //! Get current free-wheel mode.
+ bool getFreeWheel() const override;
+
+ //! This method is called by the host when the free-wheel mode changes.
+ virtual void onFreeWheelChange(bool freewheel) override = 0;
+
+
+ //! Call this to get current samplerate.
+ float getSamplerate() override;
+
+ //! This method is called by the host when the free-wheel mode changes.
+ virtual void onSamplerateChange(float samplerate) override = 0;
+
+
+ //! Call this to get current frame-size.
+ std::size_t getFramesize() override;
+
+ //! This method is called by the host when the frame-size changes.
+ virtual void onFramesizeChange(std::size_t framesize) override = 0;
+
+
+ //! Call this to get current active state
+ bool getActive() override;
+
+ //! This method is called by the host when the active state changes.
+ virtual void onActiveChange(bool active) override = 0;
+
+
+ //! This method is called by the host to get the current state for storing.
+ virtual std::string onStateSave() override = 0;
+
+ //! This method is called by the host when a new state has been loaded.
+ virtual void onStateRestore(const std::string& config) override = 0;
+
+
+ //! This is method is called by the host to get the current latency.
+ //! \param The latency in samples.
+ float getLatency() override;
+
+ //! Call this method to signal a latency change to the host.
+ //! \param latency The latency in samples.
+ void setLatency(float latency) override;
+
+
+ //! Called by the the host to get the number of audio input channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfAudioInputs() override = 0;
+
+ //! Called by the the host to get the number of audio output channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfAudioOutputs() override = 0;
+
+
+ //! Called by the the host to get the number of midi input channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfMidiInputs() override = 0;
+
+ //! Called by the the host to get the number of midi output channels.
+ //! This must remain constant during the lifespan of the plugin instance.
+ virtual std::size_t getNumberOfMidiOutputs() override = 0;
+
+
+ //! Get unique plugin id.
+ std::string getId() override = 0;
+
+ virtual void process(std::size_t pos,
+ const std::vector<MidiEvent>& input_events,
+ std::vector<MidiEvent>& output_events,
+ const std::vector<const float*>& input_samples,
+ const std::vector<float*>& output_samples,
+ std::size_t count) = 0;
+
+ //
+ // GUI
+ //
+ //! Return true if a GUI implementation is to be used.
+ virtual bool hasGUI() override
+ {
+ return false;
+ }
+
+ //! Create new window.
+ virtual void createWindow(void *parent) override {}
+
+ //! Destroy window.
+ virtual void onDestroyWindow() override {}
+
+ //! Show window.
+ virtual void onShowWindow() override {}
+
+ //! Hide window.
+ virtual void onHideWindow() override {}
+
+ //! Called regularly by host; process ui events.
+ virtual void onIdle() override {}
+
+ //! Signal new window size to host.
+ void resizeWindow(std::size_t width, std::size_t height) override;
+
+ //! Signal close window event to the host.
+ void closeWindow() override;
+
+protected:
+ bool active{false};
+
+ void updateLatency();
+ float current_latency{0.0f};
+ float update_latency{0.0f};
+
+ bool free_wheel{true};
+
+ std::vector<MidiEvent> input_events;
+
+ std::size_t pos{0};
+
+public:
+ PluginVST(audioMasterCallback audioMaster);
+ virtual ~PluginVST();
+
+ // From AudioEffect:
+ void open() override;
+ void close() override;
+ void suspend() override;
+ void resume() override;
+
+ // Callbacks:
+ void setSampleRate(float sampleRate) override;
+ void setBlockSize(VstInt32 blockSize) override;
+
+ VstInt32 getChunk(void **data, bool isPreset) override;
+ VstInt32 setChunk(void *data, VstInt32 byteSize, bool isPreset) override;
+
+ // From AudioEffectX:
+ VstInt32 canDo(char* text) override;
+
+ void processReplacing(float** inputs, float** outputs,
+ VstInt32 sampleFrames) override;
+ VstInt32 processEvents(VstEvents *events) override;
+
+ // UI
+ class UI :
+ public AEffEditor
+ {
+ public:
+ UI(PluginVST& plugin_vst);
+
+ bool open(void* ptr) override;
+ void close() override;
+ bool isOpen() override;
+ void idle() override;
+ bool getRect(ERect** rect) override;
+
+ PluginVST& plugin_vst;
+ bool is_open{false};
+
+ ERect rect{0,0,100,100};
+ };
+
+private:
+ std::shared_ptr<UI> editor;
+};
+
+AudioEffect* createEffectInstance(audioMasterCallback audioMaster);