aboutsummaryrefslogtreecommitdiffstats
path: root/lv2
diff options
context:
space:
mode:
Diffstat (limited to 'lv2')
-rw-r--r--lv2/atom/Atom.hpp208
-rw-r--r--lv2/atom/Forge.hpp547
-rw-r--r--lv2/atom/atom-test.cpp351
3 files changed, 1106 insertions, 0 deletions
diff --git a/lv2/atom/Atom.hpp b/lv2/atom/Atom.hpp
new file mode 100644
index 0000000..ab6d703
--- /dev/null
+++ b/lv2/atom/Atom.hpp
@@ -0,0 +1,208 @@
+/*
+ Copyright 2008-2017 David Robillard <http://drobilla.net>
+
+ 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.
+*/
+
+/**
+ @defgroup atompp Atom C++ bindings.
+
+ See <http://lv2plug.in/ns/ext/atom> for details.
+
+ @{
+*/
+
+#ifndef LV2_ATOM_HPP
+#define LV2_ATOM_HPP
+
+#include <cstring>
+#include <string>
+
+#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
+#include "lv2/lv2plug.in/ns/ext/atom/util.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+
+namespace lv2 {
+namespace atom {
+
+struct Atom : LV2_Atom {};
+
+template<typename AtomType>
+struct AtomWithBody : AtomType
+{
+ using Body = decltype(AtomType::body);
+
+ AtomWithBody(AtomType atom) : AtomType(atom) {}
+
+ operator const LV2_Atom&() const { return this->atom; }
+ explicit operator Body() const { return AtomType::body; }
+
+ bool operator==(const Body& body) const { return AtomType::body == body; }
+ bool operator!=(const Body& body) const { return AtomType::body != body; }
+
+ bool operator!() const { return !AtomType::body; }
+};
+
+template<typename AtomType>
+struct Primitive : AtomWithBody<AtomType>
+{
+ Primitive(AtomType atom) : AtomWithBody<AtomType>(atom) {}
+
+ bool operator<(const Primitive& rhs) const {
+ return AtomType::body < rhs.body;
+ }
+};
+
+using Int = Primitive<LV2_Atom_Int>;
+using Long = Primitive<LV2_Atom_Long>;
+using Float = Primitive<LV2_Atom_Float>;
+using Double = Primitive<LV2_Atom_Double>;
+using URID = Primitive<LV2_Atom_URID>;
+
+struct Bool : Primitive<LV2_Atom_Bool>
+{
+ Bool(LV2_Atom_Bool atom) : Primitive<LV2_Atom_Bool>(atom) {}
+
+ operator bool() const { return LV2_Atom_Bool::body; }
+ bool operator!() const { return !LV2_Atom_Bool::body; }
+};
+
+struct String : LV2_Atom_String
+{
+ operator const LV2_Atom&() const { return this->atom; }
+ operator const Atom&() const { return (const Atom&)*this; }
+
+ const char* c_str() const { return (const char*)(this + 1); }
+ char* c_str() { return (char*)(this + 1); }
+
+ bool operator==(const char* s) const { return !strcmp(c_str(), s); }
+ bool operator!=(const char* s) const { return strcmp(c_str(), s); }
+ bool operator==(const std::string& s) const { return s == c_str(); }
+ bool operator!=(const std::string& s) const { return s != c_str(); }
+};
+
+struct Literal : AtomWithBody<LV2_Atom_Literal>
+{
+ const char* c_str() const { return (const char*)(this + 1); }
+ char* c_str() { return (char*)(this + 1); }
+};
+
+template<typename CType, typename CppType, CType* Next(const CType*)>
+struct Iterator
+{
+ using value_type = CppType;
+
+ explicit Iterator(const CType* p)
+ : ptr(static_cast<const value_type*>(p))
+ {}
+
+ Iterator& operator++() {
+ ptr = static_cast<value_type*>(Next(ptr));
+ return *this;
+ }
+
+ const value_type& operator*() const { return *ptr; }
+ const value_type* operator->() const { return ptr; }
+
+ bool operator==(const Iterator& i) const { return ptr == i.ptr; }
+ bool operator!=(const Iterator& i) const { return ptr != i.ptr; }
+
+private:
+ const value_type* ptr;
+};
+
+struct Tuple : LV2_Atom_Tuple
+{
+ operator const LV2_Atom&() const { return this->atom; }
+
+ using const_iterator = Iterator<LV2_Atom, Atom, lv2_atom_tuple_next>;
+
+ const_iterator begin() const {
+ return const_iterator(lv2_atom_tuple_begin(this));
+ }
+
+ const_iterator end() const {
+ return const_iterator(
+ (const LV2_Atom*)(
+ (const char*)(this + 1) + lv2_atom_pad_size(atom.size)));
+ }
+};
+
+template<typename T>
+struct Vector : AtomWithBody<LV2_Atom_Vector>
+{
+ operator const LV2_Atom&() const { return this->atom; }
+
+ const T* data() const { return (const T*)(&body + 1); }
+ T* data() { return (T*)(&body + 1); }
+
+ const T* begin() const { return data(); }
+ const T* end() const { return (const T*)(const char*)&body + atom.size; }
+ T* begin() { return data(); }
+ T* end() { return (T*)(char*)&body + atom.size; }
+};
+
+struct Property : AtomWithBody<LV2_Atom_Property> {};
+
+struct Object : AtomWithBody<LV2_Atom_Object>
+{
+ using const_iterator = Iterator<LV2_Atom_Property_Body,
+ Property::Body,
+ lv2_atom_object_next>;
+
+ const_iterator begin() const {
+ return const_iterator(lv2_atom_object_begin(&body));
+ }
+
+ const_iterator end() const {
+ return const_iterator(
+ (const LV2_Atom_Property_Body*)(
+ (const char*)&body + lv2_atom_pad_size(atom.size)));
+ }
+};
+
+struct Event : LV2_Atom_Event {};
+
+struct Sequence : AtomWithBody<LV2_Atom_Sequence>
+{
+ using const_iterator = Iterator<LV2_Atom_Event, Event, lv2_atom_sequence_next>;
+
+ const_iterator begin() const {
+ return const_iterator(lv2_atom_sequence_begin(&body));
+ }
+
+ const_iterator end() const {
+ return const_iterator(lv2_atom_sequence_end(&body, atom.size));
+ }
+};
+
+inline bool
+operator==(const LV2_Atom& lhs, const LV2_Atom& rhs)
+{
+ return lv2_atom_equals(&lhs, &rhs);
+}
+
+inline bool
+operator!=(const LV2_Atom& lhs, const LV2_Atom& rhs)
+{
+ return !lv2_atom_equals(&lhs, &rhs);
+}
+
+} // namespace atom
+} // namespace lv2
+
+/**
+ @}
+*/
+
+#endif /* LV2_ATOM_HPP */
diff --git a/lv2/atom/Forge.hpp b/lv2/atom/Forge.hpp
new file mode 100644
index 0000000..127d635
--- /dev/null
+++ b/lv2/atom/Forge.hpp
@@ -0,0 +1,547 @@
+/*
+ Copyright 2017 David Robillard <http://drobilla.net>
+
+ 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.
+*/
+
+/**
+ @file Forge.hpp A C++ wrapper for LV2_Atom_Forge.
+
+ This file provides a wrapper for the atom forge that makes it simpler and
+ safer to write atoms in C++. It uses scoped handled for nested frames to
+ enforce correct atom structure in a safer and more readable way than the C
+ API.
+
+ A "make" interface is also provided for making primitive atoms that are
+ value types without requiring an output buffer.
+*/
+
+/**
+ @defgroup forgepp C++ Forge
+ @ingroup atompp
+ @{
+*/
+
+#ifndef LV2_ATOM_FORGE_HPP
+#define LV2_ATOM_FORGE_HPP
+
+#include <string>
+
+#include "lv2/atom/Atom.hpp"
+#include "lv2/atom/forge.h"
+
+namespace lv2 {
+namespace atom {
+
+namespace detail {
+
+template<typename Sink>
+static LV2_Atom_Forge_Ref
+write(void* const sink, const void* const buf, const uint32_t size)
+{
+ return ((Sink*)sink)->write(buf, size);
+}
+
+template<typename Sink>
+static LV2_Atom*
+deref(void* const sink, const LV2_Atom_Forge_Ref ref)
+{
+ return ((Sink*)sink)->deref(ref);
+}
+
+};
+
+/** A "forge" for creating atoms by appending to a buffer. */
+class Forge : public LV2_Atom_Forge
+{
+public:
+ using Frame = LV2_Atom_Forge_Frame;
+
+ /** Type safe wrapper for LV2_Atom_Forge_Ref. */
+ template<typename T>
+ struct Ref
+ {
+ Ref(Forge& forge, const LV2_Atom_Forge_Ref ref)
+ : forge(forge)
+ , ref(ref)
+ {}
+
+ const T& operator*() const {
+ return *(const T*)lv2_atom_forge_deref(&forge, ref);
+ }
+
+ const T* operator->() const {
+ return (const T*)lv2_atom_forge_deref(&forge, ref);
+ }
+
+ T& operator*() { return *(T*)lv2_atom_forge_deref(&forge, ref); }
+ T* operator->() { return (T*)lv2_atom_forge_deref(&forge, ref); }
+
+ operator const T&() const { return **this; }
+ operator T&() { return **this; }
+
+ Forge& forge;
+ LV2_Atom_Forge_Ref ref;
+ };
+
+ /**
+ @name Initialisation
+ @{
+ */
+
+ /** Construct a forge with uninitialized output.
+ *
+ * Either set_buffer() or set_sink() must be called before writing.
+ */
+ Forge(LV2_URID_Map* const map)
+ {
+ lv2_atom_forge_init(this, map);
+ }
+
+ /** Construct a forge for writing to a buffer. */
+ Forge(LV2_URID_Map* const map, uint8_t* const buf, const size_t size)
+ {
+ lv2_atom_forge_init(this, map);
+ set_buffer(buf, size);
+ }
+
+ /** Construct a forge for writing to a sink function. */
+ template<typename Handle>
+ Forge(LV2_URID_Map* const map,
+ LV2_Atom_Forge_Ref (*sink)(Handle, const void*, uint32_t),
+ LV2_Atom* (*deref)(Handle, LV2_Atom_Forge_Ref),
+ Handle handle)
+ {
+ lv2_atom_forge_init(this, map);
+ set_sink(sink, deref, handle);
+ }
+
+ /** Construct a forge for writing to a sink object.
+ *
+ * The sink must have write(const void*, uint32_t) and
+ * deref(LV2_Atom_Forge_Ref) methods.
+ */
+ template<typename Sink>
+ Forge(LV2_URID_Map* const map, Sink& sink)
+ {
+ lv2_atom_forge_init(this, map);
+ set_sink(sink);
+ }
+
+ /** Set the output buffer to write to. */
+ void set_buffer(uint8_t* const buf, const size_t size)
+ {
+ lv2_atom_forge_set_buffer(this, buf, size);
+ }
+
+ /** Set a sink function to write to. */
+ template<typename Handle>
+ void set_sink(LV2_Atom_Forge_Ref (*sink)(Handle, const void*, uint32_t),
+ LV2_Atom* (*deref)(Handle, LV2_Atom_Forge_Ref),
+ Handle handle)
+ {
+ lv2_atom_forge_set_sink(this, sink, deref, handle);
+ }
+
+ /** Set a sink object to write to. */
+ template<typename Sink>
+ void set_sink(Sink& sink)
+ {
+ lv2_atom_forge_set_sink(
+ this, detail::write<Sink>, detail::deref<Sink>, &sink);
+ }
+
+ /**
+ @}
+ @name Primitive Value Construction
+ @{
+ */
+
+ atom::Int make(const int32_t v) { return LV2_Atom_Int{sizeof(v), Int, v}; }
+ atom::Long make(const int64_t v) { return LV2_Atom_Long{sizeof(v), Long, v}; }
+ atom::Float make(const float v) { return LV2_Atom_Float{sizeof(v), Float, v}; }
+ atom::Double make(const double v) { return LV2_Atom_Double{sizeof(v), Double, v}; }
+ atom::Bool make(const bool v) { return LV2_Atom_Bool{sizeof(int32_t), Bool, v}; }
+
+ /**
+ @}
+ @name Low Level Output
+ @{
+ */
+
+ /** Write raw output. */
+ LV2_Atom_Forge_Ref raw(const void* const data, const uint32_t size) {
+ return lv2_atom_forge_raw(this, data, size);
+ }
+
+ /** Pad output accordingly so next write is 64-bit aligned. */
+ void pad(const uint32_t written) {
+ lv2_atom_forge_pad(this, written);
+ }
+
+ /** Write raw output, padding to 64-bits as necessary. */
+ LV2_Atom_Forge_Ref write(const void* const data, const uint32_t size)
+ {
+ return lv2_atom_forge_write(this, data, size);
+ }
+
+ /** Write a null-terminated string body. */
+ LV2_Atom_Forge_Ref string_body(const char* const str, const uint32_t len)
+ {
+ return lv2_atom_forge_string_body(this, str, len);
+ }
+
+ /**
+ @}
+ @name Atoms
+ @{
+ */
+
+ /** Write an Atom header. */
+ Ref<atom::Atom> atom(uint32_t size, uint32_t type)
+ {
+ return ref<atom::Atom>(lv2_atom_forge_atom(this, size, type));
+ }
+
+ /** Write an Atom. */
+ Ref<atom::Atom> atom(uint32_t size, uint32_t type, const void* body)
+ {
+ Ref<atom::Atom> ref(atom(size, type));
+ lv2_atom_forge_write(this, body, size);
+ return ref;
+ }
+
+ /** Write a primitive (fixed-size) atom. */
+ Ref<atom::Atom> primitive(const LV2_Atom* atom)
+ {
+ return ref<atom::Atom>(lv2_atom_forge_primitive(this, atom));
+ }
+
+ /**
+ @}
+ @name Primitives
+ @{
+ */
+
+ /** Write an atom:Int. */
+ Ref<atom::Int> write(const int32_t val)
+ {
+ return ref<atom::Int>(lv2_atom_forge_int(this, val));
+ }
+
+ /** Write an atom:Long. */
+ Ref<atom::Long> write(const int64_t val)
+ {
+ return ref<atom::Long>(lv2_atom_forge_long(this, val));
+ }
+
+ /** Write an atom:Float. */
+ Ref<atom::Float> write(const float val)
+ {
+ return ref<atom::Float>(lv2_atom_forge_float(this, val));
+ }
+
+ /** Write an atom:Double. */
+ Ref<atom::Double> write(const double val)
+ {
+ return ref<atom::Double>(lv2_atom_forge_double(this, val));
+ }
+
+ /** Write an atom:Bool. */
+ Ref<atom::Bool> write(const bool val)
+ {
+ return ref<atom::Bool>(lv2_atom_forge_bool(this, val));
+ }
+
+ /** Write an atom:URID. */
+ Ref<atom::URID> urid(const LV2_URID id)
+ {
+ return ref<atom::URID>(lv2_atom_forge_urid(this, id));
+ }
+
+ /**
+ @}
+ @name Strings
+ @{
+ */
+
+ /** Write an atom:String. Note that `str` need not be NULL terminated. */
+ Ref<atom::String> string(const char* const str, const uint32_t len)
+ {
+ return ref<atom::String>(lv2_atom_forge_string(this, str, len));
+ }
+
+ /** Write an atom:String. */
+ Ref<atom::String> string(const char* const str)
+ {
+ return string(str, strlen(str));
+ }
+
+ /** Write an atom:String. */
+ Ref<atom::String> write(const std::string& str)
+ {
+ return ref<atom::String>(
+ lv2_atom_forge_string(this, str.c_str(), str.length()));
+ }
+
+ /** Write a URI string. Note that `str` need not be NULL terminated. */
+ Ref<atom::String> uri(const char* const uri, const uint32_t len)
+ {
+ return ref<atom::String>(lv2_atom_forge_uri(this, uri, len));
+ }
+
+ /** Write a URI string. */
+ Ref<atom::String> uri(const char* const str)
+ {
+ return uri(str, strlen(str));
+ }
+
+ /** Write a URI string. */
+ Ref<atom::String> uri(const std::string& str)
+ {
+ return ref<atom::String>(
+ lv2_atom_forge_uri(this, str.c_str(), str.length()));
+ }
+
+ /** Write an atom:Path. Note that `str` need not be NULL terminated. */
+ Ref<atom::String> path(const char* const str, const uint32_t len)
+ {
+ return ref<atom::String>(lv2_atom_forge_path(this, str, len));
+ }
+
+ /** Write an atom:Path. */
+ Ref<atom::String> path(const char* const str)
+ {
+ return path(str, strlen(str));
+ }
+
+ /** Write an atom:Path. */
+ Ref<atom::String> path(const std::string& path)
+ {
+ return ref<atom::String>(
+ lv2_atom_forge_path(this, path.c_str(), path.length()));
+ }
+
+ /** Write an atom:Literal. Note that `str` need not be NULL terminated. */
+ Ref<atom::Literal> literal(const char* const str,
+ const uint32_t len,
+ const uint32_t datatype,
+ const uint32_t lang)
+ {
+ return ref<atom::Literal>(
+ lv2_atom_forge_literal(this, str, len, datatype, lang));
+ }
+
+ /** Write an atom:Literal. */
+ Ref<atom::Literal> literal(const char* const str,
+ const uint32_t datatype,
+ const uint32_t lang)
+ {
+ return literal(str, strlen(str), datatype, lang);
+ }
+
+ /** Write an atom:Literal. */
+ Ref<atom::Literal> literal(const std::string& str,
+ const uint32_t datatype,
+ const uint32_t lang)
+ {
+ return literal(str.c_str(), str.length(), datatype, lang);
+ }
+
+ /**
+ @}
+ @name Collections
+ @{
+ */
+
+ /** Base for all scoped collection handles. */
+ template<typename T>
+ struct ScopedFrame
+ {
+ public:
+ ScopedFrame(Forge& forge) : forge(forge) {}
+ ~ScopedFrame() { lv2_atom_forge_pop(&forge, &frame); }
+
+ ScopedFrame(const ScopedFrame&) = delete;
+ ScopedFrame& operator=(const ScopedFrame&) = delete;
+
+ ScopedFrame(ScopedFrame&&) = default;
+ ScopedFrame& operator=(ScopedFrame&&) = default;
+
+ T& operator*() {
+ return *(T*)lv2_atom_forge_deref(&forge, frame.ref);
+ }
+
+ const T& operator*() const {
+ return *(const T*)lv2_atom_forge_deref(&forge, frame.ref);
+ }
+
+ T* operator->() {
+ return (T*)lv2_atom_forge_deref(&forge, frame.ref);
+ }
+
+ const T* operator->() const {
+ return (const T*)lv2_atom_forge_deref(&forge, frame.ref);
+ }
+
+ Forge& forge;
+ Frame frame;
+ };
+
+ /** A scoped handle for writing a Vector.
+
+ For example, to write the vector [1, 2, 3]:
+
+ @code
+ {
+ ScopedVector vector(forge, sizeof(int32_t), forge.Int);
+ forge.write(1);
+ forge.write(2);
+ forge.write(3);
+ } // Vector finished
+ @endcode
+ */
+ template<typename T>
+ struct ScopedVector : ScopedFrame<atom::Vector<T>>
+ {
+ ScopedVector(Forge& forge, const uint32_t child_type)
+ : ScopedFrame<atom::Vector<T>>(forge)
+ {
+ lv2_atom_forge_vector_head(
+ &forge, &this->frame, sizeof(T), child_type);
+ }
+ };
+
+ /** Write a complete atom:Vector. */
+ template<typename T>
+ Ref<atom::Vector<T>> vector(const uint32_t elem_type,
+ const uint32_t n_elems,
+ const T* const elems)
+ {
+ return ref<atom::Vector<T>>(
+ lv2_atom_forge_vector(this, sizeof(T), elem_type, n_elems, elems));
+ }
+
+ /** A scoped handle for writing a Tuple.
+
+ For example, to write the tuple (1, 2.0):
+
+ @code
+ {
+ ScopedTuple tuple(forge);
+ forge.write(1);
+ forge.write(2.0f);
+ } // Tuple finished
+ @endcode
+ */
+ struct ScopedTuple : ScopedFrame<atom::Tuple>
+ {
+ ScopedTuple(Forge& forge) : ScopedFrame<atom::Tuple>(forge)
+ {
+ lv2_atom_forge_tuple(&forge, &frame);
+ }
+ };
+
+ /** A scoped handle for writing an Object.
+
+ The caller is responsible for correctly writing a sequence of
+ properties like (key, value, key, value, ...). For example, to write
+ the object [ a eg:Cat; eg:name "Hobbes" ]:
+
+ @code
+ LV2_URID eg_Cat = map("http://example.org/Cat");
+ LV2_URID eg_name = map("http://example.org/name");
+ {
+ ScopedObject object(forge, 0, eg_Cat);
+ object.key(eg_name);
+ forge.string("Hobbes", strlen("Hobbes"));
+ } // Object finished
+ @endcode
+ */
+ struct ScopedObject : ScopedFrame<atom::Object>
+ {
+ ScopedObject(Forge& forge, LV2_URID id, LV2_URID otype)
+ : ScopedFrame<atom::Object>(forge)
+ {
+ lv2_atom_forge_object(&forge, &frame, id, otype);
+ }
+
+ /** Write a property key, to be followed by the value. */
+ Ref<atom::Property::Body> key(const LV2_URID key, const LV2_URID ctx = 0)
+ {
+ return forge.ref<atom::Property::Body>(
+ lv2_atom_forge_property_head(&forge, key, ctx));
+ }
+ };
+
+ /** A scoped handle for writing a Sequence.
+
+ The caller is responsible for correctly writing a sequence of events
+ like (time, value, time, value, ...). For example:
+
+ @code
+ {
+ ScopedSequence sequence(forge, 0, eg_Cat);
+
+ sequence.frame_time(0);
+ forge.string("Bang", strlen("Bang"));
+
+ sequence.frame_time(8);
+ {
+ ScopedTuple tuple(forge);
+ forge.write(1);
+ forge.write(2.0f);
+ } // Tuple event body finished
+ } // Sequence finished
+ @endcode
+ */
+ struct ScopedSequence : ScopedFrame<atom::Sequence>
+ {
+ ScopedSequence(Forge& forge, const uint32_t unit)
+ : ScopedFrame<atom::Sequence>(forge)
+ {
+ lv2_atom_forge_sequence_head(&forge, &frame, unit);
+ }
+
+ /** Write a time stamp in frames, to be followed by the event body. */
+ Ref<int64_t> frame_time(const int64_t frames)
+ {
+ return forge.ref<int64_t>(
+ lv2_atom_forge_frame_time(&forge, frames));
+ }
+
+ /** Write a time stamp in beats, to be followed by the event body. */
+ Ref<double> beat_time(const double beats)
+ {
+ return forge.ref<double>(
+ lv2_atom_forge_beat_time(&forge, beats));
+ }
+ };
+
+ /**
+ @}
+ */
+
+private:
+ template<typename T>
+ Ref<T> ref(const LV2_Atom_Forge_Ref ref) { return Ref<T>(*this, ref); }
+};
+
+} // namespace atom
+} // namespace lv2
+
+/**
+ @}
+*/
+
+#endif /* LV2_ATOM_FORGE_HPP */
diff --git a/lv2/atom/atom-test.cpp b/lv2/atom/atom-test.cpp
new file mode 100644
index 0000000..518780e
--- /dev/null
+++ b/lv2/atom/atom-test.cpp
@@ -0,0 +1,351 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ 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.
+*/
+
+#include "lv2/atom/Atom.hpp"
+#include "lv2/atom/Forge.hpp"
+#include "lv2/atom/atom-test-utils.c"
+#include "lv2/atom/util.h"
+
+#include <cstdint>
+#include <vector>
+
+using namespace lv2::atom;
+
+static int
+test_primitives()
+{
+ LV2_URID_Map map{NULL, urid_map};
+ Forge forge{&map};
+
+ if (forge.make(int32_t(2)) < forge.make(int32_t(1)) ||
+ forge.make(int64_t(4)) < forge.make(int64_t(3)) ||
+ forge.make(6.0f) < forge.make(4.0f) ||
+ forge.make(8.0) < forge.make(7.0) ||
+ forge.make(true) < forge.make(false)) {
+ return test_fail("Primitive comparison failed\n");
+ }
+
+ return 0;
+}
+
+int
+main(void)
+{
+ if (test_primitives()) {
+ return 1;
+ }
+
+ LV2_URID_Map map = { NULL, urid_map };
+
+ LV2_URID eg_Object = urid_map(NULL, "http://example.org/Object");
+ LV2_URID eg_one = urid_map(NULL, "http://example.org/one");
+ LV2_URID eg_two = urid_map(NULL, "http://example.org/two");
+ LV2_URID eg_three = urid_map(NULL, "http://example.org/three");
+ LV2_URID eg_four = urid_map(NULL, "http://example.org/four");
+ LV2_URID eg_true = urid_map(NULL, "http://example.org/true");
+ LV2_URID eg_false = urid_map(NULL, "http://example.org/false");
+ LV2_URID eg_path = urid_map(NULL, "http://example.org/path");
+ LV2_URID eg_uri = urid_map(NULL, "http://example.org/uri");
+ LV2_URID eg_urid = urid_map(NULL, "http://example.org/urid");
+ LV2_URID eg_string = urid_map(NULL, "http://example.org/string");
+ LV2_URID eg_literal = urid_map(NULL, "http://example.org/literal");
+ LV2_URID eg_tuple = urid_map(NULL, "http://example.org/tuple");
+ LV2_URID eg_vector = urid_map(NULL, "http://example.org/vector");
+ LV2_URID eg_vector2 = urid_map(NULL, "http://example.org/vector2");
+ LV2_URID eg_seq = urid_map(NULL, "http://example.org/seq");
+
+#define BUF_SIZE 1024
+#define NUM_PROPS 15
+
+ uint8_t buf[BUF_SIZE];
+ Forge forge(&map, buf, BUF_SIZE);
+
+ Forge::ScopedObject obj(forge, 0, eg_Object);
+
+ // eg_one = (Int)1
+ obj.key(eg_one);
+ const Int& one = forge.write(1);
+ if (one != 1) {
+ return test_fail("%d != 1\n", one);
+ }
+
+ // eg_two = (Long)2
+ obj.key(eg_two);
+ const Long& two = forge.write(2L);
+ if (two != 2L) {
+ return test_fail("%ld != 2\n", two);
+ }
+
+ // eg_three = (Float)3.0
+ obj.key(eg_three);
+ const Float& three = forge.write(3.0f);
+ if (three != 3.0f) {
+ return test_fail("%f != 3.0f\n", three);
+ }
+
+ // eg_four = (Double)4.0
+ obj.key(eg_four);
+ const Double& four = forge.write(4.0);
+ if (four != 4) {
+ return test_fail("%ld != 4.0\n", four);
+ }
+
+ // eg_true = (Bool)1
+ obj.key(eg_true);
+ const Bool& t = forge.write(true);
+ if (!t) {
+ return test_fail("%ld != 1 (true)\n", t);
+ }
+
+ // eg_false = (Bool)0
+ obj.key(eg_false);
+ const Bool& f = forge.write(false);
+ if (f) {
+ return test_fail("%ld != 0 (false)\n", f);
+ }
+
+ // eg_path = (Path)"/foo/bar"
+ obj.key(eg_path);
+ const String& path = forge.path("/foo/bar");
+ if (path != "/foo/bar") {
+ return test_fail("%s != \"/foo/bar\"\n", path.c_str());
+ }
+
+ // eg_uri = (URI)"http://example.org/value"
+ obj.key(eg_uri);
+ const String& uri = forge.uri("http://lv2plug.in/");
+ if (uri != "http://lv2plug.in/") {
+ return test_fail("%s != \"http://lv2plug.in/\"\n", uri.c_str());
+ }
+
+ // eg_urid = (URID)"http://example.org/value"
+ LV2_URID eg_value = urid_map(NULL, "http://example.org/value");
+ obj.key(eg_urid);
+ LV2_Atom_URID* urid = &*forge.urid(eg_value);
+ if (urid->body != eg_value) {
+ return test_fail("%u != %u\n", urid->body, eg_value);
+ }
+
+ // eg_string = (String)"hello"
+ obj.key(eg_string);
+ const String& string = forge.string("hello", strlen("hello"));
+ if (string != "hello") {
+ return test_fail("%s != \"hello\"\n", string.c_str());
+ }
+
+ // eg_literal = (Literal)"hello"@fr
+ obj.key(eg_literal);
+ const Literal& literal = forge.literal(
+ "bonjour", 0, urid_map(NULL, "http://lexvo.org/id/term/fr"));
+ if (strcmp(literal.c_str(), "bonjour")) {
+ return test_fail("%s != \"bonjour\"\n", literal.c_str());
+ }
+
+ // eg_tuple = "foo",true
+ obj.key(eg_tuple);
+ const Tuple* tuple = NULL;
+ const String* tup0 = NULL;
+ const Bool* tup1 = NULL;
+ {
+ Forge::ScopedTuple scoped_tuple(forge);
+ tup0 = &*forge.string("foo", strlen("foo"));
+ tup1 = &*forge.write(true);
+ tuple = &*scoped_tuple;
+ }
+ std::vector<const Atom*> elements;
+ for (const Atom& atom : *tuple) {
+ elements.push_back(&atom);
+ }
+ if (elements.size() != 2) {
+ return test_fail("Short tuple iteration\n");
+ } else if (*elements[0] != *tup0) {
+ return test_fail("Incorrect first tuple element\n");
+ } else if (*elements[1] != *tup1) {
+ return test_fail("Incorrect second tuple element\n");
+ }
+
+ // eg_vector = (Vector<Int>)1,2,3,4
+ obj.key(eg_vector);
+ const int32_t elems[] = { 1, 2, 3, 4 };
+ const Vector<int32_t>& vector = forge.vector(forge.Int, 4, elems);
+ if (memcmp(elems, vector.data(), sizeof(elems))) {
+ return test_fail("Corrupt vector\n");
+ }
+ int32_t sum = 0;
+ for (const int32_t i : vector) {
+ sum += i;
+ }
+ if (sum != 1 + 2 + 3 + 4) {
+ return test_fail("Corrupt vector sum\n");
+ }
+
+ // eg_vector2 = (Vector<Int>)1,2,3,4
+ obj.key(eg_vector2);
+ const Vector<int32_t>* vector2 = NULL;
+ {
+ Forge::ScopedVector<int32_t> scoped_vector(forge, forge.Int);
+ for (unsigned e = 0; e < sizeof(elems) / sizeof(int32_t); ++e) {
+ forge.write(elems[e]);
+ }
+ vector2 = &*scoped_vector;
+ }
+ if (vector != *vector2) {
+ return test_fail("Vector != Vector2\n");
+ }
+
+ // eg_seq = (Sequence)1, 2
+ obj.key(eg_seq);
+ const Sequence* seq = NULL;
+ {
+ Forge::ScopedSequence scoped_sequence(forge, 0);
+ scoped_sequence.frame_time(0);
+ forge.write(1);
+ scoped_sequence.frame_time(1);
+ forge.write(2);
+ seq = &*scoped_sequence;
+ }
+
+ // Test equality
+ if (one == two) {
+ return test_fail("1 == 2.0\n");
+ } else if (one != one) {
+ return test_fail("1 != 1\n");
+ }
+
+ unsigned n_events = 0;
+ for (const Event& ev : *seq) {
+ if (ev.time.frames != n_events) {
+ return test_fail("Corrupt event %u has bad time\n", n_events);
+ } else if (ev.body.type != forge.Int) {
+ return test_fail("Corrupt event %u has bad type\n", n_events);
+ } else if (((LV2_Atom_Int*)&ev.body)->body != (int)n_events + 1) {
+ return test_fail("Event %u != %d\n", n_events, n_events + 1);
+ }
+ ++n_events;
+ }
+
+ unsigned n_props = 0;
+ for (const Property::Body& prop : *obj) {
+ if (!prop.key) {
+ return test_fail("Corrupt property %u has no key\n", n_props);
+ } else if (prop.context) {
+ return test_fail("Corrupt property %u has context\n", n_props);
+ }
+ ++n_props;
+ }
+
+ if (n_props != NUM_PROPS) {
+ return test_fail("Corrupt object has %u properties != %u\n",
+ n_props, NUM_PROPS);
+ }
+
+ struct {
+ const LV2_Atom* one;
+ const LV2_Atom* two;
+ const LV2_Atom* three;
+ const LV2_Atom* four;
+ const LV2_Atom* affirmative;
+ const LV2_Atom* negative;
+ const LV2_Atom* path;
+ const LV2_Atom* uri;
+ const LV2_Atom* urid;
+ const LV2_Atom* string;
+ const LV2_Atom* literal;
+ const LV2_Atom* tuple;
+ const LV2_Atom* vector;
+ const LV2_Atom* vector2;
+ const LV2_Atom* seq;
+ } matches;
+
+ memset(&matches, 0, sizeof(matches));
+
+ LV2_Atom_Object_Query q[] = {
+ { eg_one, &matches.one },
+ { eg_two, &matches.two },
+ { eg_three, &matches.three },
+ { eg_four, &matches.four },
+ { eg_true, &matches.affirmative },
+ { eg_false, &matches.negative },
+ { eg_path, &matches.path },
+ { eg_uri, &matches.uri },
+ { eg_urid, &matches.urid },
+ { eg_string, &matches.string },
+ { eg_literal, &matches.literal },
+ { eg_tuple, &matches.tuple },
+ { eg_vector, &matches.vector },
+ { eg_vector2, &matches.vector2 },
+ { eg_seq, &matches.seq },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+
+ unsigned n_matches = lv2_atom_object_query(&*obj, q);
+ for (int n = 0; n < 2; ++n) {
+ if (n_matches != n_props) {
+ return test_fail("Query failed, %u matches != %u\n",
+ n_matches, n_props);
+ } else if (one != *matches.one) {
+ return test_fail("Bad match one\n");
+ } else if (two != *matches.two) {
+ return test_fail("Bad match two\n");
+ } else if (three != *matches.three) {
+ return test_fail("Bad match three\n");
+ } else if (four != *matches.four) {
+ return test_fail("Bad match four\n");
+ } else if (t != *matches.affirmative) {
+ return test_fail("Bad match true\n");
+ } else if (f != *matches.negative) {
+ return test_fail("Bad match false\n");
+ } else if (path != *matches.path) {
+ return test_fail("Bad match path\n");
+ } else if (uri != *matches.uri) {
+ return test_fail("Bad match URI\n");
+ } else if (string != *matches.string) {
+ return test_fail("Bad match string\n");
+ } else if (literal != *matches.literal) {
+ return test_fail("Bad match literal\n");
+ } else if (*tuple != *matches.tuple) {
+ return test_fail("Bad match tuple\n");
+ } else if (vector != *matches.vector) {
+ return test_fail("Bad match vector\n");
+ } else if (vector != *matches.vector2) {
+ return test_fail("Bad match vector2\n");
+ } else if (*seq != *matches.seq) {
+ return test_fail("Bad match sequence\n");
+ }
+ memset(&matches, 0, sizeof(matches));
+ n_matches = lv2_atom_object_get((LV2_Atom_Object*)&*obj,
+ eg_one, &matches.one,
+ eg_two, &matches.two,
+ eg_three, &matches.three,
+ eg_four, &matches.four,
+ eg_true, &matches.affirmative,
+ eg_false, &matches.negative,
+ eg_path, &matches.path,
+ eg_uri, &matches.uri,
+ eg_urid, &matches.urid,
+ eg_string, &matches.string,
+ eg_literal, &matches.literal,
+ eg_tuple, &matches.tuple,
+ eg_vector, &matches.vector,
+ eg_vector2, &matches.vector2,
+ eg_seq, &matches.seq,
+ 0);
+ }
+
+ free_urid_map();
+
+ return 0;
+}