diff options
author | David Robillard <d@drobilla.net> | 2020-11-12 01:11:11 +0100 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2020-11-12 01:47:40 +0100 |
commit | bf9190ef628c1aa04791af1bd7cd4905e9c24658 (patch) | |
tree | 56114c93a8ec689cd15497059958dad03a1ee2ce /include/raul | |
parent | 496e70e420811c7d744a8bcc44a2ac1b51b676b5 (diff) | |
download | raul-bf9190ef628c1aa04791af1bd7cd4905e9c24658.tar.gz raul-bf9190ef628c1aa04791af1bd7cd4905e9c24658.tar.bz2 raul-bf9190ef628c1aa04791af1bd7cd4905e9c24658.zip |
Move includes to a conventional include directory
Diffstat (limited to 'include/raul')
-rw-r--r-- | include/raul/Array.hpp | 160 | ||||
-rw-r--r-- | include/raul/Deletable.hpp | 37 | ||||
-rw-r--r-- | include/raul/DoubleBuffer.hpp | 94 | ||||
-rw-r--r-- | include/raul/Exception.hpp | 40 | ||||
-rw-r--r-- | include/raul/Maid.hpp | 148 | ||||
-rw-r--r-- | include/raul/Noncopyable.hpp | 34 | ||||
-rw-r--r-- | include/raul/Path.hpp | 207 | ||||
-rw-r--r-- | include/raul/Process.hpp | 87 | ||||
-rw-r--r-- | include/raul/RingBuffer.hpp | 219 | ||||
-rw-r--r-- | include/raul/Semaphore.hpp | 270 | ||||
-rw-r--r-- | include/raul/Socket.hpp | 262 | ||||
-rw-r--r-- | include/raul/Symbol.hpp | 130 | ||||
-rw-r--r-- | include/raul/TimeSlice.hpp | 157 | ||||
-rw-r--r-- | include/raul/TimeStamp.hpp | 236 |
14 files changed, 2081 insertions, 0 deletions
diff --git a/include/raul/Array.hpp b/include/raul/Array.hpp new file mode 100644 index 0000000..ee50470 --- /dev/null +++ b/include/raul/Array.hpp @@ -0,0 +1,160 @@ +/* + This file is part of Raul. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_ARRAY_HPP +#define RAUL_ARRAY_HPP + +#include "raul/Maid.hpp" + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <memory> + +namespace Raul { + +/** A disposable array with size. + * + * \ingroup raul + */ +template <class T> +class Array : public Maid::Disposable +{ +public: + explicit Array(size_t size = 0) + : Maid::Disposable() + , _size(size) + , _elems(size ? new T[size] : nullptr) + { + } + + Array(size_t size, T initial_value) + : Maid::Disposable() + , _size(size) + , _elems(size ? new T[size] : nullptr) + { + if (size > 0) { + for (size_t i = 0; i < size; ++i) { + _elems[i] = initial_value; + } + } + } + + Array(const Array<T>& array) + : Maid::Disposable() + , _size(array._size) + , _elems(_size ? new T[_size] : nullptr) + { + for (size_t i = 0; i < _size; ++i) { + _elems[i] = array._elems[i]; + } + } + + ~Array() override = default; + + Array<T>& operator=(const Array<T>& array) + { + if (&array == this) { + return *this; + } + + _size = array._size; + _elems = _size ? new T[_size] : nullptr; + + for (size_t i = 0; i < _size; ++i) { + _elems[i] = array._elems[i]; + } + } + + Array(Array<T>&& array) noexcept + : _size(array._size) + , _elems(std::move(array._elems)) + { + } + + Array<T>& operator=(Array<T>&& array) noexcept + { + _size = array._size; + _elems = std::move(array._elems); + return *this; + } + + Array(size_t size, const Array<T>& contents) + : _size(size) + , _elems(size ? new T[size] : nullptr) + { + assert(contents.size() >= size); + for (size_t i = 0; i < std::min(size, contents.size()); ++i) { + _elems[i] = contents[i]; + } + } + + Array(size_t size, const Array<T>& contents, T initial_value = T()) + : _size(size) + , _elems(size ? new T[size] : nullptr) + { + const size_t end = std::min(size, contents.size()); + for (size_t i = 0; i < end; ++i) { + _elems[i] = contents[i]; + } + for (size_t i = end; i < size; ++i) { + _elems[i] = initial_value; + } + } + + virtual void alloc(size_t num_elems) { + _size = num_elems; + + if (num_elems > 0) { + _elems = std::unique_ptr<T[]>(new T[num_elems]); + } else { + _elems.reset(); + } + } + + virtual void alloc(size_t num_elems, T initial_value) { + _size = num_elems; + + if (num_elems > 0) { + _elems = std::unique_ptr<T[]>(new T[num_elems]); + for (size_t i = 0; i < _size; ++i) { + _elems[i] = initial_value; + } + } else { + _elems.reset(); + } + } + + inline size_t size() const { return _size; } + + inline T& operator[](size_t i) const { + assert(i < _size); + return _elems[i]; + } + + inline T& at(size_t i) const { + assert(i < _size); + return _elems[i]; + } + +private: + size_t _size; + std::unique_ptr<T[]> _elems; +}; + +} // namespace Raul + +#endif // RAUL_ARRAY_HPP diff --git a/include/raul/Deletable.hpp b/include/raul/Deletable.hpp new file mode 100644 index 0000000..9b80d54 --- /dev/null +++ b/include/raul/Deletable.hpp @@ -0,0 +1,37 @@ + +/* + This file is part of Raul. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_DELETABLE_HPP +#define RAUL_DELETABLE_HPP + +namespace Raul { + +/** Something with a virtual destructor. + * + * \ingroup raul + */ +class Deletable +{ +public: + Deletable() = default; + + virtual ~Deletable() = default; +}; + +} // namespace Raul + +#endif // RAUL_DELETABLE_HPP diff --git a/include/raul/DoubleBuffer.hpp b/include/raul/DoubleBuffer.hpp new file mode 100644 index 0000000..73b5565 --- /dev/null +++ b/include/raul/DoubleBuffer.hpp @@ -0,0 +1,94 @@ +/* + This file is part of Raul. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_DOUBLEBUFFER_HPP +#define RAUL_DOUBLEBUFFER_HPP + +#include <atomic> +#include <utility> + +namespace Raul { + +/** Double buffer. + * + * Can be thought of as a wrapper class to make a non-atomic type atomically + * settable (with no locking). + * + * Read/Write realtime safe, many writers safe - but set calls may fail. + * + * Space: 2*sizeof(T) + sizeof(int) + sizeof(void*) + * \ingroup raul + */ +template<typename T> +class DoubleBuffer { +public: + explicit DoubleBuffer(T val) + : _state(State::READ_WRITE) + { + _vals[0] = std::move(val); + } + + DoubleBuffer(const DoubleBuffer&) = delete; + DoubleBuffer& operator=(const DoubleBuffer&) = delete; + + inline const T& get() const { + switch (_state.load(std::memory_order_acquire)) { + case State::READ_WRITE: + case State::READ_LOCK: + return _vals[0]; + case State::WRITE_READ: + case State::LOCK_READ: + break; + } + return _vals[1]; + } + + inline bool set(T new_val) { + if (transition(State::READ_WRITE, State::READ_LOCK)) { + // Locked _vals[1] for writing + _vals[1] = std::move(new_val); + _state.store(State::WRITE_READ, std::memory_order_release); + return true; + } else if (transition(State::WRITE_READ, State::LOCK_READ)) { + // Locked _vals[0] for writing + _vals[0] = std::move(new_val); + _state.store(State::READ_WRITE, std::memory_order_release); + return true; + } + + return false; + } + +private: + enum class State { + READ_WRITE, ///< Read vals[0], Write vals[1] + READ_LOCK, ///< Read vals[0], Lock vals[1] + WRITE_READ, ///< Write vals[0], Read vals[1] + LOCK_READ ///< Lock vals[0], Read vals[1] + }; + + bool transition(State from, const State to) { + return _state.compare_exchange_strong( + from, to, std::memory_order_release, std::memory_order_relaxed); + } + + std::atomic<State> _state; + T _vals[2]; +}; + +} // namespace Raul + +#endif // RAUL_DOUBLEBUFFER_HPP diff --git a/include/raul/Exception.hpp b/include/raul/Exception.hpp new file mode 100644 index 0000000..da4a0e0 --- /dev/null +++ b/include/raul/Exception.hpp @@ -0,0 +1,40 @@ +/* + This file is part of Raul. + Copyright 2007-2012 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <exception> +#include <string> +#include <utility> + +#ifndef RAUL_EXCEPTION_HPP +#define RAUL_EXCEPTION_HPP + +namespace Raul { + +/** An exception (unexpected error). */ +class Exception : public std::exception { +public: + const char* what() const noexcept override { return _what.c_str(); } + +protected: + explicit Exception(std::string what) : _what(std::move(what)) {} + +private: + const std::string _what; +}; + +} // namespace Raul + +#endif // RAUL_EXCEPTION_HPP diff --git a/include/raul/Maid.hpp b/include/raul/Maid.hpp new file mode 100644 index 0000000..1479f71 --- /dev/null +++ b/include/raul/Maid.hpp @@ -0,0 +1,148 @@ +/* + This file is part of Raul. + Copyright 2007-2017 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_MAID_HPP +#define RAUL_MAID_HPP + +#include "raul/Deletable.hpp" +#include "raul/Noncopyable.hpp" + +#include <atomic> +#include <memory> + +namespace Raul { + +/** Explicit garbage-collector. + * + * This allows objects to be disposed of in a real-time thread, but actually + * deleted later by another thread which calls cleanup(). Disposable objects + * may be explicitly disposed by calling dispose(), or automatically managed + * with a managed_ptr which can safely be dropped in any thread, including + * real-time threads. + * + * \ingroup raul + */ +class Maid : public Noncopyable +{ +public: + /** An object that can be disposed via Maid::dispose(). */ + class Disposable : public Deletable { + public: + Disposable() = default; + Disposable(const Disposable&) = delete; + Disposable& operator=(const Disposable&) = delete; + + private: + friend class Maid; + Disposable* _maid_next{}; + }; + + /** Disposable wrapper for any type. */ + template<typename T> + class Managed : public Raul::Maid::Disposable, public T + { + public: + template<typename... Args> + explicit Managed(Args&&... args) + : T(std::forward<Args>(args)...) + {} + }; + + /** Deleter for Disposable objects. */ + template<typename T> + class Disposer { + public: + explicit Disposer(Maid* maid) : _maid(maid) {} + + Disposer() = default; + + void operator()(T* obj) { + if (_maid) { _maid->dispose(obj); } + } + + private: + Maid* _maid{nullptr}; + }; + + /** A managed pointer that automatically disposes of its contents. + * + * This is a unique_ptr so that it is possible to statically verify that + * code is real-time safe. + */ + template<typename T> using managed_ptr = std::unique_ptr<T, Disposer<T>>; + + Maid() : _disposed(nullptr) {} + + inline ~Maid() { cleanup(); } + + /** Return false iff there is currently no garbage. */ + inline bool empty() const { + return !_disposed.load(std::memory_order_relaxed); + } + + /** Enqueue an object for deletion when cleanup() is called next. + * + * This is thread-safe, and real-time safe assuming reasonably low + * contention. + */ + inline void dispose(Disposable* obj) { + if (obj) { + // Atomically add obj to the head of the disposed list + do { + obj->_maid_next = _disposed.load(std::memory_order_relaxed); + } while (!_disposed.compare_exchange_weak( + obj->_maid_next, obj, + std::memory_order_release, + std::memory_order_relaxed)); + } + } + + /** Delete all disposed objects immediately. + * + * Obviously not real-time safe, but may be called while other threads are + * calling dispose(). + */ + inline void cleanup() { + // Atomically get the head of the disposed list + Disposable* const disposed = _disposed.exchange( + nullptr, std::memory_order_acquire); + + // Free the disposed list + for (Disposable* obj = disposed; obj;) { + Disposable* const next = obj->_maid_next; + delete obj; + obj = next; + } + } + + /** Make a unique_ptr that will dispose its object when dropped. */ + template<class T, class... Args> + managed_ptr<T> make_managed(Args&&... args) { + return std::unique_ptr<T, Disposer<T> >( + new T(std::forward<Args>(args)...), + Disposer<T>(this)); + } + +private: + std::atomic<Disposable*> _disposed; +}; + +template<typename T> +using managed_ptr = Maid::managed_ptr<T>; + +} // namespace Raul + +#endif // RAUL_MAID_HPP diff --git a/include/raul/Noncopyable.hpp b/include/raul/Noncopyable.hpp new file mode 100644 index 0000000..876cee8 --- /dev/null +++ b/include/raul/Noncopyable.hpp @@ -0,0 +1,34 @@ +/* + This file is part of Raul. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_NONCOPYABLE_HPP +#define RAUL_NONCOPYABLE_HPP + +namespace Raul { + +class Noncopyable { +public: + Noncopyable(const Noncopyable&) = delete; + const Noncopyable& operator=(const Noncopyable&) = delete; + +protected: + Noncopyable() = default; + ~Noncopyable() = default; +}; + +} // namespace Raul + +#endif // RAUL_NONCOPYABLE_HPP diff --git a/include/raul/Path.hpp b/include/raul/Path.hpp new file mode 100644 index 0000000..6237ddd --- /dev/null +++ b/include/raul/Path.hpp @@ -0,0 +1,207 @@ +/* + This file is part of Raul. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_PATH_HPP +#define RAUL_PATH_HPP + +#include "raul/Exception.hpp" +#include "raul/Symbol.hpp" + +#include <algorithm> +#include <string> + +namespace Raul { + +/** A restricted path of Symbols separated by, and beginning with, "/". + * + * A Path never ends with a "/", except for the root Path "/", which is the + * only valid single-character Path. + * + * @ingroup raul + */ +class Path : public std::basic_string<char> { +public: + /** Attempt to construct an invalid Path. */ + class BadPath : public Exception { + public: + explicit BadPath(const std::string& path) : Exception(path) {} + }; + + /** Construct an uninitialzed path, because the STL is annoying. */ + Path() : std::basic_string<char>("/") {} + + /** Construct a Path from a C++ string. + * + * This will throw an exception if `path` is invalid. To avoid this, use + * is_valid() first to check. + */ + explicit Path(const std::basic_string<char>& path) + : std::basic_string<char>(path) + { + if (!is_valid(path)) { + throw BadPath(path); + } + } + + /** Construct a Path from a C string. + * + * This will throw an exception if `path` is invalid. To avoid this, use + * is_valid() first to check. + */ + explicit Path(const char* path) + : std::basic_string<char>(path) + { + if (!is_valid(path)) { + throw BadPath(path); + } + } + + /** Copy a Path. + * + * Note this is faster than constructing a Path from another Path's string + * since validation is unnecessary. + */ + Path(const Path& path) = default; + + /** Return true iff `c` is a valid Path character. */ + static inline bool is_valid_char(char c) { + return c == '/' || Symbol::is_valid_char(c); + } + + /** Return true iff `str` is a valid Path. */ + static inline bool is_valid(const std::basic_string<char>& str) { + if (str.empty() || (str[0] != '/')) { + return false; // Must start with '/' + } + + if (str != "/" && *str.rbegin() == '/') { + return false; // Must not end with '/' except for the root + } + + for (size_t i = 1; i < str.length(); ++i) { + if (!is_valid_char(str[i])) { + return false; // All characters must be /, _, a-z, A-Z, 0-9 + } else if (str[i - 1] == '/') { + if (str[i] == '/') { + return false; // Must not contain "//" + } else if (!Symbol::is_valid_start_char(str[i])) { + return false; // Invalid symbol start character (digit) + } + } + } + + return true; + } + + /** Return true iff this path is the root path ("/"). */ + inline bool is_root() const { return *this == "/"; } + + /** Return true iff this path is a child of `parent` at any depth. */ + inline bool is_child_of(const Path& parent) const { + const std::string parent_base = parent.base(); + return substr(0, parent_base.length()) == parent_base; + } + + /** Return true iff this path is a parent of `child` at any depth. */ + inline bool is_parent_of(const Path& child) const { + return child.is_child_of(*this); + } + + /** Return the symbol of this path (everything after the last '/'). + * + * This is what is called the "basename" for file paths, the "method name" + * for OSC paths, and so on. Since the root path does not have a symbol, + * this does not return a Raul::Symbol but may return the empty string. + */ + inline const char* symbol() const { + if (!is_root()) { + const size_t last_sep = rfind('/'); + if (last_sep != std::string::npos) { + return c_str() + last_sep + 1; + } + } + return ""; + } + + /** Return the parent's path. + * + * Calling this on the path "/" will return "/". + * This is the (deepest) "container path" for OSC paths. + */ + inline Path parent() const { + if (is_root()) { + return *this; + } else { + const size_t first_sep = find('/'); + const size_t last_sep = find_last_of('/'); + return (first_sep == last_sep) + ? Path("/") : Path(substr(0, last_sep)); + } + } + + /** Return a child Path of this path. */ + inline Path child(const Path& p) const { + return p.is_root() ? *this : Path(base() + p.substr(1)); + } + + /** Return a direct child Path of this Path with the given Symbol. */ + inline Path child(const Raul::Symbol& symbol) const { + return Path(base() + symbol.c_str()); + } + + /** Return path with a trailing "/". + * + * The returned string is such that appending a valid Symbol to it is + * guaranteed to form a valid path. + */ + inline std::string base() const { + if (is_root()) { + return *this; + } else { + return *this + '/'; + } + } + + /** Return the lowest common ancestor of a and b. */ + static inline Path lca(const Path& a, const Path& b) { + const size_t len = std::min(a.length(), b.length()); + size_t last_sep = 0; + for (size_t i = 0; i < len; ++i) { + if (a[i] == '/' && b[i] == '/') { + last_sep = i; + } + if (a[i] != b[i]) { + break; + } + } + + if (last_sep <= 1) { + return Path("/"); + } + + return Path(a.substr(0, last_sep)); + } + + /** Return true iff `child` is equal to, or a descendant of `parent`. */ + static inline bool descendant_comparator(const Path& parent, + const Path& child) { + return (child == parent || child.is_child_of(parent)); + } +}; + +} // namespace Raul + +#endif // RAUL_PATH_HPP diff --git a/include/raul/Process.hpp b/include/raul/Process.hpp new file mode 100644 index 0000000..717a900 --- /dev/null +++ b/include/raul/Process.hpp @@ -0,0 +1,87 @@ +/* + This file is part of Raul. + Copyright 2007-2015 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_PROCESS_HPP +#define RAUL_PROCESS_HPP + +#include "raul/Noncopyable.hpp" + +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> + +#include <string> + +namespace Raul { + +/** A child process. + * + * \ingroup raul + */ +class Process : Noncopyable +{ +public: + /** Launch a sub process. + * + * @param argv List of arguments, where argv[0] is the command name. + * @return True on success. + */ + static bool launch(const char* const argv[]) { + // Use the same double fork() trick as JACK to prevent zombie children + const int child = fork(); + + if (child == 0) { + // (in child) + + // Close all nonstandard file descriptors + struct rlimit max_fds{}; + getrlimit(RLIMIT_NOFILE, &max_fds); + for (rlim_t fd = 3; fd < max_fds.rlim_cur; ++fd) { + close(static_cast<int>(fd)); + } + + // Fork child + const int grandchild = fork(); + switch (grandchild) { + case 0: + // (in grandchild) + setsid(); + execvp(argv[0], const_cast<char*const*>(argv)); + _exit(-1); + + case -1: + // Fork failed, there is no grandchild + _exit(-1); + + default: + // Fork succeeded, return grandchild PID + _exit(grandchild); + } + } else if (child < 0) { + // Fork failed, there is no child + return false; + } + + return true; + } + +private: + Process() = default; +}; + +} // namespace Raul + +#endif // RAUL_PROCESS_HPP diff --git a/include/raul/RingBuffer.hpp b/include/raul/RingBuffer.hpp new file mode 100644 index 0000000..a7bbfb7 --- /dev/null +++ b/include/raul/RingBuffer.hpp @@ -0,0 +1,219 @@ +/* + This file is part of Raul. + Copyright 2007-2012 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_RINGBUFFER_HPP +#define RAUL_RINGBUFFER_HPP + +#include "raul/Noncopyable.hpp" + +#include <atomic> +#include <cassert> +#include <cstdint> +#include <cstdlib> +#include <cstring> +#include <memory> + +namespace Raul { + +/** + A lock-free RingBuffer. + + Thread-safe with a single reader and single writer, and real-time safe + on both ends. + + @ingroup raul +*/ +class RingBuffer : public Noncopyable { +public: + /** + Create a new RingBuffer. + @param size Size in bytes (note this may be rounded up). + */ + explicit RingBuffer(uint32_t size) + : _write_head(0) + , _read_head(0) + , _size(next_power_of_two(size)) + , _size_mask(_size - 1) + , _buf(new char[_size]) + { + assert(read_space() == 0); + assert(write_space() == _size - 1); + } + + /** + Destroy a RingBuffer. + */ + inline ~RingBuffer() = default; + + /** + Reset (empty) the RingBuffer. + + This method is NOT thread-safe, it may only be called when there are no + readers or writers. + */ + inline void reset() { + _write_head = 0; + _read_head = 0; + } + + /** + Return the number of bytes of space available for reading. + */ + inline uint32_t read_space() const { + return read_space_internal(_read_head, _write_head); + } + + /** + Return the number of bytes of space available for writing. + */ + inline uint32_t write_space() const { + return write_space_internal(_read_head, _write_head); + } + + /** + Return the capacity (i.e. total write space when empty). + */ + inline uint32_t capacity() const { return _size - 1; } + + /** + Read from the RingBuffer without advancing the read head. + */ + inline uint32_t peek(uint32_t size, void* dst) { + return peek_internal(_read_head, _write_head, size, dst); + } + + /** + Read from the RingBuffer and advance the read head. + */ + inline uint32_t read(uint32_t size, void* dst) { + const uint32_t r = _read_head; + const uint32_t w = _write_head; + + if (peek_internal(r, w, size, dst)) { + std::atomic_thread_fence(std::memory_order_acquire); + _read_head = (r + size) & _size_mask; + return size; + } else { + return 0; + } + } + + /** + Skip data in the RingBuffer (advance read head without reading). + */ + inline uint32_t skip(uint32_t size) { + const uint32_t r = _read_head; + const uint32_t w = _write_head; + if (read_space_internal(r, w) < size) { + return 0; + } + + std::atomic_thread_fence(std::memory_order_acquire); + _read_head = (r + size) & _size_mask; + return size; + } + + /** + Write data to the RingBuffer. + */ + inline uint32_t write(uint32_t size, const void* src) { + const uint32_t r = _read_head; + const uint32_t w = _write_head; + if (write_space_internal(r, w) < size) { + return 0; + } + + if (w + size <= _size) { + memcpy(&_buf[w], src, size); + std::atomic_thread_fence(std::memory_order_release); + _write_head = (w + size) & _size_mask; + } else { + const uint32_t this_size = _size - w; + assert(this_size < size); + assert(w + this_size <= _size); + memcpy(&_buf[w], src, this_size); + memcpy(&_buf[0], + static_cast<const char*>(src) + this_size, + size - this_size); + std::atomic_thread_fence(std::memory_order_release); + _write_head = size - this_size; + } + + return size; + } + +private: + static inline uint32_t next_power_of_two(uint32_t size) { + // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + size--; + size |= size >> 1U; + size |= size >> 2U; + size |= size >> 4U; + size |= size >> 8U; + size |= size >> 16U; + size++; + return size; + } + + inline uint32_t write_space_internal(uint32_t r, uint32_t w) const { + if (r == w) { + return _size - 1; + } else if (r < w) { + return ((r - w + _size) & _size_mask) - 1; + } else { + return (r - w) - 1; + } + } + + inline uint32_t read_space_internal(uint32_t r, uint32_t w) const { + if (r < w) { + return w - r; + } else { + return (w - r + _size) & _size_mask; + } + } + + inline uint32_t peek_internal(uint32_t r, uint32_t w, + uint32_t size, void* dst) const { + if (read_space_internal(r, w) < size) { + return 0; + } + + if (r + size < _size) { + memcpy(dst, &_buf[r], size); + } else { + const uint32_t first_size = _size - r; + memcpy(dst, &_buf[r], first_size); + memcpy(static_cast<char*>(dst) + first_size, + &_buf[0], + size - first_size); + } + + return size; + } + + mutable uint32_t _write_head; ///< Read index into _buf + mutable uint32_t _read_head; ///< Write index into _buf + + const uint32_t _size; ///< Size (capacity) in bytes + const uint32_t _size_mask; ///< Mask for fast modulo + + const std::unique_ptr<char[]> _buf; ///< Contents +}; + +} // namespace Raul + +#endif // RAUL_RINGBUFFER_HPP diff --git a/include/raul/Semaphore.hpp b/include/raul/Semaphore.hpp new file mode 100644 index 0000000..ee2325f --- /dev/null +++ b/include/raul/Semaphore.hpp @@ -0,0 +1,270 @@ +/* + This file is part of Raul. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_SEMAPHORE_HPP +#define RAUL_SEMAPHORE_HPP + +#ifdef __APPLE__ +# include <mach/mach.h> +#elif defined(_WIN32) +# define NOMINMAX +# include <windows.h> +#else +# include <cerrno> +# include <ctime> +# include <semaphore.h> +#endif + +#include <chrono> +#include <stdexcept> + +namespace Raul { + +/** + Unnamed (process local) counting semaphore. + + The civilized person's synchronisation primitive. A counting semaphore is + an integer which is always non-negative, so, an attempted decrement (or + "wait") will block if the value is 0, until another thread does an increment + (or "post"). + + At least on Lignux, the main advantage of this is that it is fast and the + only safe way to reliably signal from a real-time audio thread. The + counting semantics also complement ringbuffers of events nicely. +*/ +class Semaphore +{ +public: + /** + Create a new semaphore. + + Chances are you want 1 wait() per 1 post(), an initial value of 0. + */ + explicit Semaphore(unsigned initial) + : _sem() + { + if (!init(initial)) { + throw std::runtime_error("Failed to create semaphore"); + } + } + + inline Semaphore(const Semaphore&) = delete; + inline Semaphore& operator=(const Semaphore&) = delete; + + inline ~Semaphore() { + destroy(); + } + + /** Destroy and reset to a new initial value. */ + inline void reset(unsigned initial) { + destroy(); + init(initial); + } + + /** Post/Increment/Signal */ + inline void post(); + + /** Wait/Decrement. Return false on error. */ + inline bool wait(); + + /** Attempt Wait/Decrement. Return true iff decremented. */ + inline bool try_wait(); + + /** Wait for at most `ms` milliseconds. Return true iff decremented. */ + template<class Rep, class Period> + inline bool timed_wait(const std::chrono::duration<Rep, Period>& wait); + +private: + inline bool init(unsigned initial); + inline void destroy(); + +#if defined(__APPLE__) + semaphore_t _sem; // sem_t is a worthless broken mess on OSX +#elif defined(_WIN32) + HANDLE _sem; // types are overrated anyway +#else + sem_t _sem; +#endif +}; + +#ifdef __APPLE__ + +inline bool +Semaphore::init(unsigned initial) +{ + if (semaphore_create(mach_task_self(), &_sem, SYNC_POLICY_FIFO, int(initial))) { + return false; + } + return true; +} + +inline void +Semaphore::destroy() +{ + semaphore_destroy(mach_task_self(), _sem); +} + +inline void +Semaphore::post() +{ + semaphore_signal(_sem); +} + +inline bool +Semaphore::wait() +{ + if (semaphore_wait(_sem) != KERN_SUCCESS) { + return false; + } + return true; +} + +inline bool +Semaphore::try_wait() +{ + const mach_timespec_t zero = { 0, 0 }; + return semaphore_timedwait(_sem, zero) == KERN_SUCCESS; +} + +template<class Rep, class Period> +inline bool +Semaphore::timed_wait(const std::chrono::duration<Rep, Period>& wait) +{ + namespace chr = std::chrono; + + const chr::seconds sec(chr::duration_cast<chr::seconds>(wait)); + const chr::nanoseconds nsec(wait - sec); + + const mach_timespec_t t = { static_cast<unsigned>(sec.count()), + static_cast<int>(nsec.count()) }; + return semaphore_timedwait(_sem, t) == KERN_SUCCESS; +} + +#elif defined(_WIN32) + +inline bool +Semaphore::init(unsigned initial) +{ + if (!(_sem = CreateSemaphore(NULL, (LONG)initial, LONG_MAX, NULL))) { + return false; + } + return true; +} + +inline void +Semaphore::destroy() +{ + CloseHandle(_sem); +} + +inline void +Semaphore::post() +{ + ReleaseSemaphore(_sem, 1, NULL); +} + +inline bool +Semaphore::wait() +{ + if (WaitForSingleObject(_sem, INFINITE) != WAIT_OBJECT_0) { + return false; + } + return true; +} + +inline bool +Semaphore::try_wait() +{ + return WaitForSingleObject(_sem, 0) == WAIT_OBJECT_0; +} + +template<class Rep, class Period> +inline bool +Semaphore::timed_wait(const std::chrono::duration<Rep, Period>& wait) +{ + namespace chr = std::chrono; + + const chr::milliseconds ms(chr::duration_cast<chr::milliseconds>(wait)); + return WaitForSingleObject( + _sem, static_cast<DWORD>(ms.count())) == WAIT_OBJECT_0; +} + +#else /* !defined(__APPLE__) && !defined(_WIN32) */ + +inline bool +Semaphore::init(unsigned initial) +{ + return !sem_init(&_sem, 0, initial); +} + +inline void +Semaphore::destroy() +{ + sem_destroy(&_sem); +} + +inline void +Semaphore::post() +{ + sem_post(&_sem); +} + +inline bool +Semaphore::wait() +{ + while (sem_wait(&_sem)) { + if (errno != EINTR) { + return false; // We are all doomed + } + /* Otherwise, interrupted (rare/weird), so try again. */ + } + + return true; +} + +inline bool +Semaphore::try_wait() +{ + return (sem_trywait(&_sem) == 0); +} + +template<class Rep, class Period> +inline bool +Semaphore::timed_wait(const std::chrono::duration<Rep, Period>& wait) +{ + namespace chr = std::chrono; + + // Use clock_gettime to ensure sem_timedwait uses the same clock + struct timespec time{}; + clock_gettime(CLOCK_REALTIME, &time); + + const auto now(chr::seconds(time.tv_sec) + chr::nanoseconds(time.tv_nsec)); + const auto end(now + wait); + + const chr::seconds end_sec(chr::duration_cast<chr::seconds>(end)); + const chr::nanoseconds end_nsec(end - end_sec); + + const struct timespec ts_end = { static_cast<time_t>(end_sec.count()), + static_cast<long>(end_nsec.count()) }; + + return (sem_timedwait(&_sem, &ts_end) == 0); +} + +#endif + +} // namespace Raul + +#endif // RAUL_SEMAPHORE_HPP diff --git a/include/raul/Socket.hpp b/include/raul/Socket.hpp new file mode 100644 index 0000000..579a5cd --- /dev/null +++ b/include/raul/Socket.hpp @@ -0,0 +1,262 @@ +/* + This file is part of Raul. + Copyright 2007-2015 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_SOCKET_HPP +#define RAUL_SOCKET_HPP + +#include "raul/Noncopyable.hpp" + +#include <netdb.h> +#include <netinet/in.h> +#include <poll.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include <cerrno> +#include <cstdint> +#include <cstdlib> +#include <cstring> +#include <memory> +#include <string> + +namespace Raul { + +/** A safe and simple interface for UNIX or TCP sockets. */ +class Socket : public Raul::Noncopyable { +public: + enum class Type { + UNIX, + TCP + }; + + /** Create a new unbound/unconnected socket of a given type. */ + explicit Socket(Type t); + + /** Wrap an existing open socket. */ + Socket(Type t, + std::string uri, + struct sockaddr* addr, + socklen_t addr_len, + int fd); + + Socket(const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + + ~Socket(); + + /** Bind a server socket to an address. + * @param uri Address URI, e.g. unix:///tmp/foo, or tcp://hostname:1234. + * Use "*" as hostname to listen on all interfaces. + * @return True on success. + */ + bool bind(const std::string& uri); + + /** Connect a client socket to a server address. + * @param uri Address URI, e.g. unix:///tmp/foo or tcp://somehost:1234 + * @return True on success. + */ + bool connect(const std::string& uri); + + /** Mark server socket as passive to listen for incoming connections. + * @return True on success. + */ + bool listen(); + + /** Accept a connection. + * @return An new open socket for the connection. + */ + std::shared_ptr<Socket> accept(); + + /** Return the file descriptor for the socket. */ + int fd() const { return _sock; } + + const std::string& uri() const { return _uri; } + + /** Close the socket. */ + void close(); + + /** Shut down the socket. + * This terminates any connections associated with the sockets, and will + * (unlike close()) cause a poll on the socket to return. + */ + void shutdown(); + +private: + bool set_addr(const std::string& uri); + + std::string _uri; + struct sockaddr* _addr; + socklen_t _addr_len; + Type _type; + int _sock; +}; + +#ifndef NI_MAXHOST +# define NI_MAXHOST 1025 +#endif + +inline +Socket::Socket(Type t) + : _uri(t == Type::UNIX ? "unix:" : "tcp:") + , _addr(nullptr) + , _addr_len(0) + , _type(t) + , _sock(-1) +{ + switch (t) { + case Type::UNIX: + _sock = socket(AF_UNIX, SOCK_STREAM, 0); + break; + case Type::TCP: + _sock = socket(AF_INET, SOCK_STREAM, 0); + break; + } +} + +inline +Socket::Socket(Type t, + std::string uri, + struct sockaddr* addr, + socklen_t addr_len, + int fd) + : _uri(std::move(uri)) + , _addr(addr) + , _addr_len(addr_len) + , _type(t) + , _sock(fd) +{ +} + +inline +Socket::~Socket() +{ + free(_addr); + close(); +} + +inline bool +Socket::set_addr(const std::string& uri) +{ + free(_addr); + if (_type == Type::UNIX && uri.substr(0, strlen("unix://")) == "unix://") { + const std::string path = uri.substr(strlen("unix://")); + auto* uaddr = static_cast<struct sockaddr_un*>( + calloc(1, sizeof(struct sockaddr_un))); + uaddr->sun_family = AF_UNIX; + strncpy(uaddr->sun_path, path.c_str(), sizeof(uaddr->sun_path) - 1); + _uri = uri; + _addr = reinterpret_cast<sockaddr*>(uaddr); + _addr_len = sizeof(struct sockaddr_un); + return true; + } else if (_type == Type::TCP && uri.find("://") != std::string::npos) { + const std::string authority = uri.substr(uri.find("://") + 3); + const size_t port_sep = authority.find(':'); + if (port_sep == std::string::npos) { + return false; + } + + std::string host = authority.substr(0, port_sep); + const std::string port = authority.substr(port_sep + 1); + if (host.empty() || host == "*") { + host = "0.0.0.0"; // INADDR_ANY + } + + struct addrinfo* ainfo = nullptr; + if (getaddrinfo(host.c_str(), port.c_str(), nullptr, &ainfo)) { + return false; + } + + _uri = uri; + _addr = static_cast<struct sockaddr*>(malloc(ainfo->ai_addrlen)); + _addr_len = ainfo->ai_addrlen; + memcpy(_addr, ainfo->ai_addr, ainfo->ai_addrlen); + freeaddrinfo(ainfo); + return true; + } + return false; +} + +inline bool +Socket::bind(const std::string& uri) +{ + return set_addr(uri) && ::bind(_sock, _addr, _addr_len) != -1; +} + +inline bool +Socket::connect(const std::string& uri) +{ + return set_addr(uri) && ::connect(_sock, _addr, _addr_len) != -1; +} + +inline bool +Socket::listen() +{ + if (::listen(_sock, 64) == -1) { + return false; + } else { + return true; + } +} + +inline std::shared_ptr<Socket> +Socket::accept() +{ + socklen_t client_addr_len = _addr_len; + auto* const client_addr = + static_cast<struct sockaddr*>(calloc(1, client_addr_len)); + + const int conn = ::accept(_sock, client_addr, &client_addr_len); + if (conn == -1) { + free(client_addr); + return std::shared_ptr<Socket>(); + } + + std::string client_uri = _uri; + if (_type != Type::UNIX) { + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; + if (!getnameinfo(client_addr, client_addr_len, + host, sizeof(host), serv, sizeof(serv), 0)) { + const std::string scheme = _uri.substr(0, _uri.find(':')); + client_uri = scheme + "://" + host + ":" + serv; + } + } + + return std::make_shared<Socket>( + _type, client_uri, client_addr, client_addr_len, conn); +} + +inline void +Socket::close() +{ + if (_sock != -1) { + ::close(_sock); + _sock = -1; + } +} + +inline void +Socket::shutdown() +{ + if (_sock != -1) { + ::shutdown(_sock, SHUT_RDWR); + } +} + +} // namespace Raul + +#endif // RAUL_SOCKET_HPP diff --git a/include/raul/Symbol.hpp b/include/raul/Symbol.hpp new file mode 100644 index 0000000..70062be --- /dev/null +++ b/include/raul/Symbol.hpp @@ -0,0 +1,130 @@ +/* + This file is part of Raul. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_SYMBOL_HPP +#define RAUL_SYMBOL_HPP + +#include "raul/Exception.hpp" + +#include <string> + +namespace Raul { + +/** A restricted string which is a valid C identifier and Path component. + * + * A Symbol is a very restricted string suitable for use as an identifier. + * It is a valid LV2 symbol, URI fragment, filename, OSC path fragment, + * and identifier for virtually all programming languages. + * + * Valid characters are _, a-z, A-Z, 0-9, except the first character which + * must not be 0-9. + * + * @ingroup raul + */ +class Symbol : public std::basic_string<char> { +public: + /** Attempt to construct an invalid Symbol. */ + class BadSymbol : public Exception { + public: + explicit BadSymbol(const std::string& symbol) : Exception(symbol) {} + }; + + /** Construct a Symbol from a C++ string. + * + * This will throw an exception if `symbol` is invalid. To avoid this, + * use is_valid() first to check. + */ + explicit Symbol(const std::basic_string<char>& symbol) + : std::basic_string<char>(symbol) + { + if (!is_valid(symbol)) { + throw BadSymbol(symbol); + } + } + + /** Construct a Symbol from a C string. + * + * This will throw an exception if `symbol` is invalid. To avoid this, + * use is_valid() first to check. + */ + explicit Symbol(const char* symbol) + : std::basic_string<char>(symbol) + { + if (!is_valid(symbol)) { + throw BadSymbol(symbol); + } + } + + /** Copy a Symbol. + * + * Note this is faster than constructing a Symbol from another Symbol's + * string since validation is unnecessary. + */ + Symbol(const Symbol& symbol) = default; + + /** Return true iff `c` is a valid Symbol start character. */ + static inline bool is_valid_start_char(char c) { + return (c == '_') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + /** Return true iff `c` is a valid Symbol character. */ + static inline bool is_valid_char(char c) { + return is_valid_start_char(c) || (c >= '0' && c <= '9'); + } + + /** Return true iff `str` is a valid Symbol. */ + static inline bool is_valid(const std::basic_string<char>& str) { + if (str.empty() || (str[0] >= '0' && str[0] <= '9')) { + return false; // Must start with a letter or underscore + } + + for (auto c : str) { + if (!is_valid_char(c)) { + return false; // All characters must be _, a-z, A-Z, 0-9 + } + } + + return true; + } + + /** Convert a string to a valid symbol. + * + * This will make a best effort at turning `str` into a complete, valid + * Symbol, and will always return one. + */ + static inline Symbol symbolify(const std::basic_string<char>& in) { + if (in.empty()) { + return Symbol("_"); + } + + std::basic_string<char> out(in); + for (size_t i = 0; i < in.length(); ++i) { + if (!is_valid_char(out[i])) { + out[i] = '_'; + } + } + + if (is_valid_start_char(out[0])) { + return Symbol(out); + } else { + return Symbol(std::string("_") + out); + } + } +}; + +} // namespace Raul + +#endif // RAUL_SYMBOL_HPP diff --git a/include/raul/TimeSlice.hpp b/include/raul/TimeSlice.hpp new file mode 100644 index 0000000..7758602 --- /dev/null +++ b/include/raul/TimeSlice.hpp @@ -0,0 +1,157 @@ +/* + This file is part of Raul. + Copyright 2007-2012 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_TIMESLICE_HPP +#define RAUL_TIMESLICE_HPP + +#include "raul/Noncopyable.hpp" +#include "raul/TimeStamp.hpp" + +#include <cassert> +#include <cmath> + +namespace Raul { + +/* FIXME: all the conversion here is wrong now */ + +/** A duration of time, with conversion between tick time and beat time. + * + * This is a slice along a single timeline (ie t=0 in ticks and t=0 in beats + * are equal). Relation to an external time base (e.g. Jack frame time) is + * represented by frame_offset (the idea is that this holds all the information + * necessary for passing to run() methods so they know the current state of + * things WRT time). + * + * This class handles conversion between two units of time: musical + * (beat) time, and real (tick) time. Real time is discrete, the smallest + * unit of time is the 'tick' (usually audio frames or MIDI ticks). Beat time + * is stored as a double (to be independent of any rates or timer precision). + * + * This caches as many values as possible to make calls less expensive, pass it + * around by reference, not value. + * + * \ingroup raul + */ +class TimeSlice : public Noncopyable { +public: + TimeSlice(uint32_t rate, uint32_t ppqn, double bpm) + : _tick_rate(rate) + , _beat_rate(60.0/bpm) + , _start_ticks(Raul::TimeUnit(Raul::TimeUnit::FRAMES, rate), 0, 0) + , _length_ticks(TimeUnit(TimeUnit::FRAMES, rate), 0, 0) + , _start_beats(TimeUnit(TimeUnit::BEATS, ppqn), 0, 0) + , _length_beats(TimeUnit(TimeUnit::BEATS, ppqn), 0, 0) + , _offset_ticks(TimeUnit(TimeUnit::FRAMES, rate), 0, 0) + {} + + /** Set the start and length of the slice. + * + * Note that external offset is not affected by this, don't forget to reset + * the offset each cycle! + */ + void set_slice(TimeStamp start, TimeDuration length) { + assert(start.unit() == ticks_unit()); + assert(length.unit() == ticks_unit()); + _start_ticks = start; + _length_ticks = length; + update_beat_time(); + } + + void set_length(TimeDuration length) { + assert(length.unit() == ticks_unit()); + _length_ticks = length; + _length_beats = ticks_to_beats(_length_ticks); + } + + bool contains(TimeStamp time) const { + return (time >= start_ticks() && time < start_ticks() + length_ticks()); + } + + double tick_rate() const { return _tick_rate; } + double beat_rate() const { return _beat_rate; } + double bpm() const { return 60/_beat_rate; } + + void set_tick_rate(double tick_rate) { + _tick_rate = tick_rate; + update_beat_time(); + } + + void set_bpm(double bpm) { + _beat_rate = 60.0/bpm; + update_beat_time(); + } + + inline TimeStamp beats_to_seconds(TimeStamp beats) const { + return TimeStamp(real_unit(), beats.to_double() * 1/_beat_rate); + } + + inline TimeStamp beats_to_ticks(TimeStamp beats) const { + return TimeStamp(ticks_unit(), beats.to_double() * _beat_rate * _tick_rate); + } + + inline TimeStamp ticks_to_seconds(TimeStamp ticks) const { + return {real_unit(), ticks.ticks() * 1/_tick_rate}; + } + + inline TimeStamp ticks_to_beats(TimeStamp ticks) const { + return {beats_unit(), ticks.ticks() * 1/_tick_rate * _beat_rate}; + } + + /** Start of current sub-cycle in ticks */ + inline TimeStamp start_ticks() const { return _start_ticks; } + + /** Length of current sub-cycle in ticks */ + inline TimeDuration length_ticks() const { return _length_ticks; } + + /** Start of current sub-cycle in beats */ + inline TimeStamp start_beats() const { return _start_beats; } + + /** Length of current sub-cycle in beats */ + inline TimeDuration length_beats() const { return _length_beats; } + + /** Set the offset between real-time and timeslice-time. */ + inline void set_offset(TimeDuration offset) { _offset_ticks = offset; } + + /** Offset relative to external (e.g Jack) time */ + inline TimeDuration offset_ticks() const { return _offset_ticks; } + + inline TimeUnit beats_unit() const { return _start_beats.unit(); } + inline TimeUnit ticks_unit() const { return _start_ticks.unit(); } + + static inline TimeUnit real_unit() { return {TimeUnit::SECONDS, 0}; } + +private: + inline void update_beat_time() { + _start_beats = ticks_to_beats(_start_ticks); + _length_beats = ticks_to_beats(_length_ticks); + } + + // Rate/Tempo + double _tick_rate; ///< Tick rate in Hz (e.g. sample rate) + double _beat_rate; ///< Beat rate in Hz + + // Current time + TimeStamp _start_ticks; ///< Current window start in ticks + TimeDuration _length_ticks; ///< Current window length in ticks + TimeStamp _start_beats; ///< Current window start in beats + TimeDuration _length_beats; ///< Current window length in beats + + TimeDuration _offset_ticks; ///< Offset to global time +}; + +} // namespace Raul + +#endif // RAUL_TIMESLICE_HPP diff --git a/include/raul/TimeStamp.hpp b/include/raul/TimeStamp.hpp new file mode 100644 index 0000000..d8d4939 --- /dev/null +++ b/include/raul/TimeStamp.hpp @@ -0,0 +1,236 @@ +/* + This file is part of Raul. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_TIMESTAMP_HPP +#define RAUL_TIMESTAMP_HPP + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstdint> +#include <iostream> +#include <limits> + +namespace Raul { + +/** A type of time stamp + * \ingroup raul + */ +class TimeUnit { +public: + enum Type { + FRAMES, + BEATS, + SECONDS + }; + + /** `ppt` (parts per tick) is the sample rate for FRAMES, PPQN for BEATS, + * and ignored for SECONDS. + */ + inline TimeUnit(Type type, uint32_t ppt) + : _type(type) + , _ppt(ppt) + { + assert(type == SECONDS || ppt != 0); + _type = type; + _ppt = ppt; + } + + static inline TimeUnit frames(uint32_t srate) { return {FRAMES, srate}; } + static inline TimeUnit beats(uint32_t ppqn) { return {BEATS, ppqn}; } + static inline TimeUnit seconds() { return {BEATS, std::numeric_limits<uint32_t>::max()}; } + + inline Type type() const { return _type; } + inline uint32_t ppt() const { return _ppt; } + + inline bool operator==(const TimeUnit& rhs) const { + return (_type == rhs._type && _ppt == rhs._ppt); + } + + inline bool operator!=(const TimeUnit& rhs) const { + return (_type != rhs._type || _ppt != rhs._ppt); + } + +private: + Type _type; + uint32_t _ppt; +}; + +/** A real-time time stamp (possible units: frame, absolute (s), or beat). + * + * This is a uint32_t:uint32_t fixed point representation, capable of + * sub-sample accurate frame time, beat time (at any remotely sane + * tempo and sample rate), and absolute time. The absolute time (seconds) + * is compatible with standard OSC/NTP time stamps. + * + * \ingroup raul + */ +class TimeStamp { +public: + explicit TimeStamp(TimeUnit unit, uint32_t ticks = 0, uint32_t subticks = 0) + : _ticks(ticks) + , _subticks(subticks) + , _unit(unit) + {} + + inline TimeStamp(TimeUnit unit, double dec) + : _ticks(0u) + , _subticks(0u) + , _unit(unit) + { + dec = std::max(0.0, dec); + dec = std::min(double(std::numeric_limits<uint32_t>::max()), dec); + double integral = 0.0; + const double fractional = modf(dec, &integral); + _ticks = static_cast<uint32_t>(integral); + _subticks = static_cast<uint32_t>(fractional * unit.ppt()); + } + + inline TimeStamp(const TimeStamp&) = default; + TimeStamp& operator=(const TimeStamp&) = default; + + inline TimeUnit unit() const { return _unit; } + inline uint32_t ticks() const { return _ticks; } + inline uint32_t subticks() const { return _subticks; } + + inline double to_double() const { + return _ticks + (_subticks / static_cast<double>(_unit.ppt())); + } + + inline bool is_zero() const { + return _ticks == 0 && _subticks == 0; + } + + inline TimeStamp& operator=(uint32_t ticks) { + _ticks = ticks; + _subticks = 0; + return *this; + } + + inline bool operator==(const TimeStamp& rhs) const { + return _ticks == rhs._ticks + && _subticks == rhs._subticks + && _unit == rhs._unit; + } + + inline bool operator!=(const TimeStamp& rhs) const { + return !operator==(rhs); + } + + inline bool operator<(const TimeStamp& rhs) const { + assert(_unit == rhs._unit); + return (_ticks < rhs._ticks + || (_ticks == rhs._ticks && _subticks < rhs._subticks)); + } + + inline bool operator>(const TimeStamp& rhs) const { + assert(_unit == rhs._unit); + return (_ticks > rhs._ticks + || (_ticks == rhs._ticks && _subticks > rhs._subticks)); + } + + inline bool operator<=(const TimeStamp& rhs) const { + return (*this == rhs) || ((*this) < rhs); + } + + inline bool operator>=(const TimeStamp& rhs) const { + return (*this == rhs) || ((*this) > rhs); + } + + inline TimeStamp& operator+=(const TimeStamp& rhs) { + assert(_unit == rhs._unit); + _ticks += rhs._ticks; + if (_subticks + rhs._subticks <= _unit.ppt()) { + _subticks += rhs._subticks; + } else if (rhs._subticks > 0) { + ++_ticks; + _subticks = rhs._subticks + _subticks - _unit.ppt(); + } + return *this; + } + + inline TimeStamp& operator-=(const TimeStamp& rhs) { + assert(_unit == rhs._unit); + assert(rhs <= *this); + _ticks -= rhs._ticks; + if (_subticks >= rhs._subticks) { + _subticks -= rhs._subticks; + } else if (rhs._subticks > 0) { + --_ticks; + _subticks = _unit.ppt() - (rhs._subticks - _subticks); + } + return *this; + } + + inline TimeStamp operator+(const TimeStamp& rhs) const { + assert(_unit == rhs._unit); + TimeStamp result = *this; + result += rhs; + return result; + } + + inline TimeStamp operator-(const TimeStamp& rhs) const { + assert(_unit == rhs._unit); + TimeStamp result = *this; + result -= rhs; + return result; + } + +private: + uint32_t _ticks; + uint32_t _subticks; + TimeUnit _unit; +}; + +static inline std::ostream& +operator<<(std::ostream& os, const TimeStamp& t) +{ + os << t.ticks() << ":" << t.subticks(); + switch (t.unit().type()) { + case TimeUnit::FRAMES: + os << " frames"; + break; + case TimeUnit::BEATS: + os << " beats"; + break; + case TimeUnit::SECONDS: + os << " seconds"; + break; + } + return os; +} + +class FrameStamp : public TimeStamp { +public: + explicit FrameStamp(uint32_t rate, uint32_t ticks = 0, uint32_t subticks = 0) + : TimeStamp(TimeUnit(TimeUnit::FRAMES, rate), ticks, subticks) + {} +}; + +class BeatStamp : public TimeStamp { +public: + explicit BeatStamp(uint32_t ppqn, uint32_t ticks = 0, uint32_t subticks = 0) + : TimeStamp(TimeUnit(TimeUnit::BEATS, ppqn), ticks, subticks) + {} +}; + +/** Same thing as TimeStamp really, but makes code clearer and enforces + * correct semantics via type safety */ +using TimeDuration = TimeStamp; + +} // namespace Raul + +#endif // RAUL_TIMESTAMP_HPP |