diff --git a/components/rgbd-sources/src/sources/screencapture/screencapture.cpp b/components/rgbd-sources/src/sources/screencapture/screencapture.cpp
index 6ade3f14ecc1cd858d3597d17f3308eebae722eb..fef465c2067120c55641828632c6ee8190c2bd8c 100644
--- a/components/rgbd-sources/src/sources/screencapture/screencapture.cpp
+++ b/components/rgbd-sources/src/sources/screencapture/screencapture.cpp
@@ -216,6 +216,8 @@ bool ScreenCapture::retrieve(ftl::rgbd::Frame &frame) {
 	if (!ready_) return false;
 	cv::Mat img;
 
+	// TODO: Proper, press, release and motion behaviour
+	// Also, render the cursor location
 	if (host_->value("enable_touch", false)) {
 		if (frame.changed(Channel::Touch)) {
 			const auto &touches = frame.get<std::vector<ftl::codecs::Touch>>(Channel::Touch);
diff --git a/components/structures/include/ftl/data/frame.hpp b/components/structures/include/ftl/data/frame.hpp
deleted file mode 100644
index c304e4e97b87eb310e1e1912f17cb680cb9d1e28..0000000000000000000000000000000000000000
--- a/components/structures/include/ftl/data/frame.hpp
+++ /dev/null
@@ -1,567 +0,0 @@
-#pragma once
-#ifndef _FTL_DATA_FRAME_HPP_
-#define _FTL_DATA_FRAME_HPP_
-
-#include <ftl/configuration.hpp>
-#include <ftl/exception.hpp>
-
-#include <ftl/codecs/channels.hpp>
-#include <ftl/codecs/codecs.hpp>
-//#include <ftl/codecs/packet.hpp>
-#include <ftl/utility/vectorbuffer.hpp>
-
-#include <type_traits>
-#include <array>
-//#include <list>
-#include <unordered_map>
-
-#include <Eigen/Eigen>
-
-namespace ftl {
-namespace data {
-
-/**
- * Manage a set of channels corresponding to a single frame. There are three
- * kinds of channel in a frame: 1) the data type of interest (DoI)
- * (eg. audio, video, etc), 2) Persistent state and 3) Generic meta data.
- * The DoI is a template arg and could be in any form. Different DoIs will use
- * different frame instances, ie. audio and video frame types. Persistent state
- * may or may not change between frames but is always available. Finally,
- * generic data is a small amount of information about the primary data that may
- * or may not exist each frame, and is not required to exist.
- * 
- * There is no specification for frame rates, intervals or synchronisation at
- * this level. A frame is a quantum of data of any temporal size which can be
- * added to a FrameSet to be synchronised with other frames.
- * 
- * Use this template class either by inheriting it or just by providing the
- * template arguments. It is not abstract and can work directly.
- * 
- * The template DATA parameter must be a class or struct that implements three
- * methods: 1) `const T& at<T>()` to cast to const type, 2) `T& at<T>()` to cast
- * to non-const type, and 3) `T& make<T>() to create data as a type.
- * 
- * The STATE parameter must be an instance of `ftl::data::FrameState`.
- * 
- * @see ftl::data::FrameState
- * @see ftl::data::FrameSet
- * @see ftl::rgbd::FrameState
- * @see ftl::rgbd::Frame
- */
-template <int BASE, int N, typename STATE, typename DATA>
-class Frame {
-	static_assert(N <= ftl::codecs::Channels<BASE>::kMax, "Too many channels requested");
-
-public:
-	Frame() : origin_(nullptr) {}
-	Frame(Frame &&f) {
-		f.swapTo(*this);
-		f.reset();
-	}
-
-	Frame &operator=(Frame &&f) {
-		f.swapTo(*this);
-		f.reset();
-		return *this;
-	}
-
-	// Prevent frame copy, instead use a move.
-	Frame(const Frame &)=delete;
-	Frame &operator=(const Frame &)=delete;
-
-	/**
-	 * Perform a buffer swap of the selected channels. This is intended to be
-	 * a copy from `this` to the passed frame object but by buffer swap
-	 * instead of memory copy, meaning `this` may become invalid afterwards.
-	 * It is a complete frame swap.
-	 */
-	void swapTo(ftl::codecs::Channels<BASE>, Frame &);
-
-	void swapTo(Frame &);
-
-	/**
-	 * Swap only selected channels to another frame, without resetting or swapping
-	 * any other aspect of the frame. Unlike swapTo, this isn't intended to
-	 * be a complete frame swap.
-	 */
-	void swapChannels(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &);
-
-	void swapChannels(ftl::codecs::Channel, ftl::codecs::Channel);
-
-	/**
-	 * Does a host or device memory copy into the given frame.
-	 */
-	void copyTo(ftl::codecs::Channels<BASE>, Frame &);
-
-	/**
-	 * Create a channel but without any format.
-	 */
-	template <typename T> T &create(ftl::codecs::Channel c);
-
-	/**
-	 * Set the value of a channel. Some channels should not be modified via the
-	 * non-const get method, for example the data channels.
-	 */
-	template <typename T> void create(ftl::codecs::Channel channel, const T &value);
-
-	/**
-	 * Append encoded data for a channel. This will move the data, invalidating
-	 * the original packet structure. It is to be used to allow data that is
-	 * already encoded to be transmitted or saved again without re-encoding.
-	 * A called to `create` will clear all encoded data for that channel.
-	 */
-	//void pushPacket(ftl::codecs::Channel c, ftl::codecs::Packet &pkt);
-
-	/**
-	 * Obtain a list of any existing encodings for this channel.
-	 */
-	//const std::list<ftl::codecs::Packet> &getPackets(ftl::codecs::Channel c) const;
-
-	/**
-	 * Clear any existing encoded packets. Used when the channel data is
-	 * modified and the encodings are therefore out-of-date.
-	 */
-	//void clearPackets(ftl::codecs::Channel c);
-
-	/**
-	 * Reset all channels without releasing memory.
-	 */
-	void reset();
-
-	/**
-	 * Reset all channels and release memory.
-	 */
-	//void resetFull();
-
-	/**
-	 * Is there valid data in channel (either host or gpu). This does not
-	 * verify that any memory or data exists for the channel.
-	 */
-	bool hasChannel(ftl::codecs::Channel channel) const {
-		int c = static_cast<int>(channel);
-		if (c >= 64 && c <= 68) return true;
-		else if (c >= 2048) return data_channels_.has(channel);
-		else if (c < BASE || c >= BASE+N) return false;
-		else return channels_.has(channel);
-	}
-
-	/**
-	 * Obtain a mask of all available channels in the frame.
-	 */
-	inline ftl::codecs::Channels<BASE> getChannels() const { return channels_; }
-
-	inline ftl::codecs::Channels<2048> getDataChannels() const { return data_channels_; }
-
-	/**
-	 * Does this frame have new data for a channel. This is compared with a
-	 * previous frame and always returns true for image data. It may return
-	 * false for persistent state data (calibration, pose etc).
-	 */
-	inline bool hasChanged(ftl::codecs::Channel c) const {
-		return (static_cast<int>(c) < 64) ? true : state_.hasChanged(c);
-	}
-
-	/**
-	 * Method to get reference to the channel content.
-	 * @param	Channel type
-	 * @return	Const reference to channel data
-	 * 
-	 * Result is valid only if hasChannel() is true. Host/Gpu transfer is
-	 * performed, if necessary, but with a warning since an explicit upload or
-	 * download should be used.
-	 */
-	template <typename T> const T& get(ftl::codecs::Channel channel) const;
-
-	/**
-	 * Get the data from a data channel. This only works for the data channels
-	 * and will throw an exception with any others.
-	 */
-	template <typename T> void get(ftl::codecs::Channel channel, T &params) const;
-
-	/**
-	 * Method to get reference to the channel content. The channel must already
-	 * have been created of this will throw an exception. See `getBuffer` to
-	 * get access before creation.
-	 * 
-	 * @param	Channel type
-	 * @return	Reference to channel data
-	 * 
-	 * Result is valid only if hasChannel() is true.
-	 */
-	template <typename T> T& get(ftl::codecs::Channel channel);
-
-	/**
-	 * Method to get reference to the channel content. Unlike `get`, the channel
-	 * must not already exist as this is intended as a pre-create step that
-	 * allocates memory and populates the buffer. `create` must then be called
-	 * to make the channel available.
-	 * 
-	 * @param	Channel type
-	 * @return	Reference to channel data
-	 * 
-	 * Result is valid only if hasChannel() is true.
-	 */
-	template <typename T> T& getBuffer(ftl::codecs::Channel channel);
-
-	/**
-	 * Wrapper accessor function to get frame pose.
-	 */
-	const Eigen::Matrix4d &getPose() const;
-
-	/**
-	 * Change the pose of the origin state and mark as changed.
-	 */
-	void setPose(const Eigen::Matrix4d &pose, bool mark=true);
-
-	/**
-	 * Change the pose of the origin state and mark as changed.
-	 */
-	void patchPose(const Eigen::Matrix4d &pose);
-
-	/**
-	 * Wrapper to access left settings channel.
-	 */
-	const typename STATE::Settings &getSettings() const;
-
-	const typename STATE::Settings &getLeft() const;
-	const typename STATE::Settings &getRight() const;
-
-	void setLeft(const typename STATE::Settings &);
-	void setRight(const typename STATE::Settings &);
-
-	/**
-	 * Change left settings in the origin state. This should send
-	 * the changed parameters in reverse through a stream.
-	 */
-	void setSettings(const typename STATE::Settings &c);
-
-	/**
-	 * Dump the current frame config object to a json string.
-	 */
-	std::string getConfigString() const;
-
-	/**
-	 * Access the raw data channel vector object.
-	 */
-	const std::vector<unsigned char> &getRawData(ftl::codecs::Channel c) const;
-
-	/**
-	 * Provide raw data for a data channel.
-	 */
-	void createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v);
-
-	/**
-	 * Wrapper to access a config property. If the property does not exist or
-	 * is not of the requested type then the returned optional is false.
-	 */
-	template <class T>
-	std::optional<T> get(const std::string &name) { return state_.template get<T>(name); }
-
-	/**
-	 * Modify a config property. This does not modify the origin config so
-	 * will not get transmitted over the stream.
-	 * @todo Modify origin to send backwards over a stream.
-	 */
-	template <typename T>
-	void set(const std::string &name, T value) { state_.set(name, value); }
-
-	/**
-	 * Set the persistent state for the frame. This can only be done after
-	 * construction or a reset. Multiple calls to this otherwise will throw
-	 * an exception. The pointer must remain valid for the life of the frame.
-	 */
-	void setOrigin(STATE *state);
-
-	/**
-	 * Get the original frame state object. This can be a nullptr in some rare
-	 * cases. When wishing to change state (pose, calibration etc) then those
-	 * changes must be done on this origin, either directly or via wrappers.
-	 */
-	STATE *origin() const { return origin_; }
-
-	//ftl::codecs::Channels<BASE> completed;
-
-	typedef STATE State;
-
-	int id;
-
-protected:
-	/* Lookup internal state for a given channel. */
-	inline DATA &getData(ftl::codecs::Channel c) { return data_[static_cast<unsigned int>(c)-BASE]; }
-	inline const DATA &getData(ftl::codecs::Channel c) const { return data_[static_cast<unsigned int>(c)-BASE]; }
-
-private:
-	std::array<DATA, N> data_;
-
-	std::unordered_map<int, std::vector<unsigned char>> data_data_;
-
-	ftl::codecs::Channels<BASE> channels_;	// Does it have a channel
-	ftl::codecs::Channels<2048> data_channels_;
-
-	// Persistent state
-	STATE state_;
-	STATE *origin_;
-};
-
-}
-}
-
-// ==== Implementations ========================================================
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::reset() {
-	origin_ = nullptr;
-	channels_.clear();
-	data_channels_.clear();
-	for (size_t i=0u; i<ftl::codecs::Channels<BASE>::kMax; ++i) {
-		data_[i].reset();
-	}
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::swapTo(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &f) {
-	f.reset();
-	f.origin_ = origin_;
-	f.state_ = state_;
-
-	// For all channels in this frame object
-	for (auto c : channels_) {
-		// Should we swap this channel?
-		if (channels.has(c)) {
-			f.channels_ += c;
-			// TODO: Make sure this does a move not copy
-			std::swap(f.getData(c),getData(c));
-		}
-	}
-
-	f.data_data_ = std::move(data_data_);
-	f.data_channels_ = data_channels_;
-	data_channels_.clear();
-	channels_.clear();
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::swapTo(Frame<BASE,N,STATE,DATA> &f) {
-	swapTo(ftl::codecs::Channels<BASE>::All(), f);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::swapChannels(ftl::codecs::Channel a, ftl::codecs::Channel b) {
-	auto &m1 = getData(a);
-	auto &m2 = getData(b);
-
-	auto temp = std::move(m2);
-	m2 = std::move(m1);
-	m1 = std::move(temp);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::swapChannels(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &f) {
-	// For all channels in this frame object
-	for (auto c : channels_) {
-		// Should we swap this channel?
-		if (channels.has(c)) {
-			f.channels_ += c;
-			// TODO: Make sure this does a move not copy
-			std::swap(f.getData(c),getData(c));
-			channels_ -= c;
-		}
-	}
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::copyTo(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &f) {
-	f.reset();
-	f.origin_ = origin_;
-	f.state_ = state_;
-
-	// For all channels in this frame object
-	for (auto c : channels_) {
-		// Should we copy this channel?
-		if (channels.has(c)) {
-			f.channels_ += c;
-			f.getData(c) = getData(c);
-		}
-	}
-
-	f.data_data_ = data_data_;
-	f.data_channels_ = data_channels_;
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-// cppcheck-suppress *
-template <typename T>
-T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel) {
-	if (channel == ftl::codecs::Channel::None) {
-		throw FTL_Error("Attempting to get channel 'None'");
-	}
-
-	// Add channel if not already there
-	if (!channels_.has(channel)) {
-		throw FTL_Error("Frame channel does not exist: " << (int)channel);
-	}
-
-	return getData(channel).template as<T>();
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-// cppcheck-suppress *
-template <typename T>
-T& ftl::data::Frame<BASE,N,STATE,DATA>::getBuffer(ftl::codecs::Channel channel) {
-	if (channel == ftl::codecs::Channel::None) {
-		throw ftl::exception("Attempting to get channel 'None'");
-	}
-
-	if (channels_.has(channel)) {
-		throw ftl::exception(ftl::Formatter() << "Cannot getBuffer on existing channel: " << (int)channel);
-	}
-
-	if (static_cast<int>(channel) < BASE || static_cast<int>(channel) >= BASE+32) {
-		throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel);
-	}
-
-	return getData(channel).template make<T>();
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-// cppcheck-suppress *
-template <typename T>
-const T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel) const {
-	if (channel == ftl::codecs::Channel::None) {
-		throw FTL_Error("Attempting to get channel 'None'");
-	} else if (channel == ftl::codecs::Channel::Pose) {
-		return state_.template as<T,ftl::codecs::Channel::Pose>();
-	} else if (channel == ftl::codecs::Channel::Calibration) {
-		return state_.template as<T,ftl::codecs::Channel::Calibration>();
-	} else if (channel == ftl::codecs::Channel::Calibration2) {
-		return state_.template as<T,ftl::codecs::Channel::Calibration2>();
-	} else if (channel == ftl::codecs::Channel::Configuration) {
-		return state_.template as<T,ftl::codecs::Channel::Configuration>();
-	}
-
-	// Add channel if not already there
-	if (!channels_.has(channel)) {
-		throw FTL_Error("Frame channel does not exist: " << (int)channel);
-	}
-
-	return getData(channel).template as<T>();
-}
-
-// Default data channel implementation
-template <int BASE, int N, typename STATE, typename DATA>
-// cppcheck-suppress *
-template <typename T>
-void ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel, T &params) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel");
-	if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist");
-
-	const auto &i = data_data_.find(static_cast<int>(channel));
-	if (i == data_data_.end()) throw FTL_Error("Data channel does not exist");
-
-	auto unpacked = msgpack::unpack((const char*)(*i).second.data(), (*i).second.size());
-	unpacked.get().convert(params);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-// cppcheck-suppress *
-template <typename T>
-T &ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel c) {
-	if (c == ftl::codecs::Channel::None) {
-		throw FTL_Error("Cannot create a None channel");
-	}
-	channels_ += c;
-
-	auto &m = getData(c);
-	return m.template make<T>();
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-// cppcheck-suppress *
-template <typename T>
-void ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel channel, const T &value) {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel");
-
-	data_channels_ += channel;
-
-	auto &v = *std::get<0>(data_data_.insert({static_cast<int>(channel),{}}));
-	v.second.resize(0);
-	ftl::util::FTLVectorBuffer buf(v.second);
-	msgpack::pack(buf, value);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::setOrigin(STATE *state) {
-	if (origin_ != nullptr) {
-		throw FTL_Error("Can only set origin once after reset");
-	}
-
-	origin_ = state;
-	state_ = *state;
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-const Eigen::Matrix4d &ftl::data::Frame<BASE,N,STATE,DATA>::getPose() const {
-	return get<Eigen::Matrix4d>(ftl::codecs::Channel::Pose);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-const typename STATE::Settings &ftl::data::Frame<BASE,N,STATE,DATA>::getLeft() const {
-	return get<typename STATE::Settings>(ftl::codecs::Channel::Calibration);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-const typename STATE::Settings &ftl::data::Frame<BASE,N,STATE,DATA>::getSettings() const {
-	return get<typename STATE::Settings>(ftl::codecs::Channel::Calibration);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-const typename STATE::Settings &ftl::data::Frame<BASE,N,STATE,DATA>::getRight() const {
-	return get<typename STATE::Settings>(ftl::codecs::Channel::Calibration2);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::setPose(const Eigen::Matrix4d &pose, bool mark) {
-	if (origin_) {
-		if (mark) origin_->setPose(pose);
-		else origin_->getPose() = pose;
-	}
-	state_.setPose(pose);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::patchPose(const Eigen::Matrix4d &pose) {
-	state_.getPose() = pose * state_.getPose();
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::setLeft(const typename STATE::Settings &c) {
-	if (origin_) origin_->setLeft(c);
-	state_.setLeft(c);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::setRight(const typename STATE::Settings &c) {
-	if (origin_) origin_->setRight(c);
-	state_.setRight(c);
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-std::string ftl::data::Frame<BASE,N,STATE,DATA>::getConfigString() const {
-	return ftl::config::dumpJSON(get<nlohmann::json>(ftl::codecs::Channel::Configuration));
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-const std::vector<unsigned char> &ftl::data::Frame<BASE,N,STATE,DATA>::getRawData(ftl::codecs::Channel channel) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Non data channel");
-	if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist");
-
-	return data_data_.at(static_cast<int>(channel));
-}
-
-template <int BASE, int N, typename STATE, typename DATA>
-void ftl::data::Frame<BASE,N,STATE,DATA>::createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v) {
-	data_data_.insert({static_cast<int>(c), v});
-	data_channels_ += c;
-}
-
-#endif // _FTL_DATA_FRAME_HPP_
diff --git a/components/structures/include/ftl/data/frameset.hpp b/components/structures/include/ftl/data/frameset.hpp
deleted file mode 100644
index 4de7035f584e8d26490b799b3bd1846c25486fc3..0000000000000000000000000000000000000000
--- a/components/structures/include/ftl/data/frameset.hpp
+++ /dev/null
@@ -1,244 +0,0 @@
-#ifndef _FTL_DATA_FRAMESET_HPP_
-#define _FTL_DATA_FRAMESET_HPP_
-
-#include <ftl/threads.hpp>
-#include <ftl/timer.hpp>
-#include <ftl/data/frame.hpp>
-#include <functional>
-
-//#include <opencv2/opencv.hpp>
-#include <vector>
-
-namespace ftl {
-namespace data {
-
-// Allows a latency of 20 frames maximum
-//static const size_t kMaxFramesets = 15;
-static const size_t kMaxFramesInSet = 32;
-
-enum class FSFlag : int {
-	STALE = 0,
-	PARTIAL = 1
-};
-
-/**
- * Represents a set of synchronised frames, each with two channels. This is
- * used to collect all frames from multiple computers that have the same
- * timestamp.
- */
-template <typename FRAME>
-class FrameSet {
-	public:
-
-	int id=0;
-	int64_t timestamp;				// Millisecond timestamp of all frames
-	int64_t originClockDelta;
-	std::vector<FRAME> frames;
-	std::atomic<int> count;				// Number of valid frames
-	std::atomic<unsigned int> mask;		// Mask of all sources that contributed
-	//bool stale;						// True if buffers have been invalidated
-	SHARED_MUTEX mtx;
-
-	Eigen::Matrix4d pose;  // Set to identity by default.
-
-	inline int64_t localTimestamp() const { return timestamp + originClockDelta; }
-
-	void set(FSFlag f) { flags_ |= (1 << static_cast<int>(f)); }
-	void clear(FSFlag f) { flags_ &= ~(1 << static_cast<int>(f)); }
-	bool test(FSFlag f) const { return flags_ & (1 << static_cast<int>(f)); }
-	void clearFlags() { flags_ = 0; }
-
-	/**
-	 * Move the entire frameset to another frameset object. This will
-	 * invalidate the current frameset object as all memory buffers will be
-	 * moved.
-	 */
-	void swapTo(ftl::data::FrameSet<FRAME> &);
-
-    typedef FRAME Frame;
-    typedef std::function<bool(ftl::data::FrameSet<FRAME> &)> Callback;
-
-	/**
-	 * Get the data from a data channel. This only works for the data channels
-	 * and will throw an exception with any others.
-	 */
-	template <typename T> void get(ftl::codecs::Channel channel, T &params) const;
-
-	/**
-	 * Set the value of a channel. Some channels should not be modified via the
-	 * non-const get method, for example the data channels.
-	 */
-	template <typename T> void create(ftl::codecs::Channel channel, const T &value);
-
-	/**
-	 * Access the raw data channel vector object.
-	 */
-	const std::vector<unsigned char> &getRawData(ftl::codecs::Channel c) const;
-
-	/**
-	 * Provide raw data for a data channel.
-	 */
-	void createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v);
-
-	/**
-	 * Is there valid data in channel (either host or gpu). This does not
-	 * verify that any memory or data exists for the channel.
-	 */
-	inline bool hasChannel(ftl::codecs::Channel channel) const {
-		int c = static_cast<int>(channel);
-		if (c == 66) return true;
-		else if (c >= 2048) return data_channels_.has(channel);
-		return false;
-	}
-
-	/**
-	 * Check that a given frame is valid in this frameset.
-	 */
-	inline bool hasFrame(size_t ix) const { return (1 << ix) & mask; }
-
-	/**
-	 * Get the first valid frame in this frameset. No valid frames throws an
-	 * exception.
-	 */
-	FRAME &firstFrame();
-
-	const FRAME &firstFrame() const;
-
-	void clearData() {
-		data_.clear();
-		data_channels_.clear();
-	}
-
-	ftl::codecs::Channels<2048> getDataChannels() const { return data_channels_; }
-
-	private:
-	std::unordered_map<int, std::vector<unsigned char>> data_;
-	ftl::codecs::Channels<2048> data_channels_;
-	std::atomic<int> flags_;
-};
-
-/**
- * Callback type for receiving video frames.
- */
-//typedef std::function<bool(ftl::rgbd::FrameSet &)> VideoCallback;
-
-/**
- * Abstract class for any generator of FrameSet structures. A generator
- * produces (decoded) frame sets at regular frame intervals depending on the
- * global timer settings. The `onFrameSet` callback may be triggered from any
- * thread and also may drop frames and not be called for a given timestamp.
- */
-template <typename FRAMESET>
-class Generator {
-	public:
-	Generator() {}
-	virtual ~Generator() {}
-
-	/** Number of frames in last frameset. This can change over time. */
-	virtual size_t size()=0;
-
-	/**
-	 * Get the persistent state object for a frame. An exception is thrown
-	 * for a bad index.
-	 */
-	virtual typename FRAMESET::Frame::State &state(size_t ix)=0;
-
-	inline typename FRAMESET::Frame::State &operator[](int ix) { return state(ix); }
-
-	/** Register a callback to receive new frame sets. */
-	virtual void onFrameSet(const typename FRAMESET::Callback &)=0;
-};
-
-}
-}
-
-// === Implementations =========================================================
-
-template <typename FRAME>
-void ftl::data::FrameSet<FRAME>::swapTo(ftl::data::FrameSet<FRAME> &fs) {
-	//UNIQUE_LOCK(fs.mtx, lk);
-	std::unique_lock<std::shared_mutex> lk(fs.mtx);
-
-	//if (fs.frames.size() != frames.size()) {
-		// Assume "this" is correct and "fs" is not.
-		fs.frames.resize(frames.size());
-	//}
-
-	fs.timestamp = timestamp;
-	fs.count = static_cast<int>(count);
-	fs.flags_ = (int)flags_;
-	fs.mask = static_cast<unsigned int>(mask);
-	fs.id = id;
-	fs.pose = pose;
-
-	for (size_t i=0; i<frames.size(); ++i) {
-		frames[i].swapTo(ftl::codecs::Channels<0>::All(), fs.frames[i]);
-	}
-
-	std::swap(fs.data_, data_);
-	fs.data_channels_ = data_channels_;
-	data_channels_.clear();
-
-	set(ftl::data::FSFlag::STALE);
-}
-
-// Default data channel implementation
-template <typename FRAME>
-// cppcheck-suppress *
-template <typename T>
-void ftl::data::FrameSet<FRAME>::get(ftl::codecs::Channel channel, T &params) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel");
-	if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist");
-
-	const auto &i = data_.find(static_cast<int>(channel));
-	if (i == data_.end()) throw FTL_Error("Data channel does not exist");
-
-	auto unpacked = msgpack::unpack((const char*)(*i).second.data(), (*i).second.size());
-	unpacked.get().convert(params);
-}
-
-template <typename FRAME>
-// cppcheck-suppress *
-template <typename T>
-void ftl::data::FrameSet<FRAME>::create(ftl::codecs::Channel channel, const T &value) {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel");
-
-	data_channels_ += channel;
-
-	auto &v = *std::get<0>(data_.insert({static_cast<int>(channel),{}}));
-	v.second.resize(0);
-	ftl::util::FTLVectorBuffer buf(v.second);
-	msgpack::pack(buf, value);
-}
-
-template <typename FRAME>
-const std::vector<unsigned char> &ftl::data::FrameSet<FRAME>::getRawData(ftl::codecs::Channel channel) const {
-	if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Non data channel");
-	if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist");
-
-	return data_.at(static_cast<int>(channel));
-}
-
-template <typename FRAME>
-void ftl::data::FrameSet<FRAME>::createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v) {
-	data_.insert({static_cast<int>(c), v});
-	data_channels_ += c;
-}
-
-template <typename FRAME>
-FRAME &ftl::data::FrameSet<FRAME>::firstFrame() {
-	for (size_t i=0; i<frames.size(); ++i) {
-		if (hasFrame(i)) return frames[i];
-	}
-	throw FTL_Error("No frames in frameset");
-}
-
-template <typename FRAME>
-const FRAME &ftl::data::FrameSet<FRAME>::firstFrame() const {
-	for (size_t i=0; i<frames.size(); ++i) {
-		if (hasFrame(i)) return frames[i];
-	}
-	throw FTL_Error("No frames in frameset");
-}
-
-#endif  // _FTL_DATA_FRAMESET_HPP_
diff --git a/components/structures/include/ftl/data/framestate.hpp b/components/structures/include/ftl/data/framestate.hpp
deleted file mode 100644
index 378a37f3d49406c9dec46114e04da8c1e6cbc21d..0000000000000000000000000000000000000000
--- a/components/structures/include/ftl/data/framestate.hpp
+++ /dev/null
@@ -1,302 +0,0 @@
-#ifndef _FTL_DATA_FRAMESTATE_HPP_
-#define _FTL_DATA_FRAMESTATE_HPP_
-
-#include <ftl/configuration.hpp>
-#include <ftl/exception.hpp>
-#include <ftl/codecs/channels.hpp>
-#include <Eigen/Eigen>
-#include <array>
-#include <optional>
-#include <string>
-
-namespace ftl {
-namespace data {
-
-/**
- * Represent state that is persistent across frames. Such state may or may not
- * change from one frame to the next so a record of what has changed must be
- * kept. Changing state should be done at origin and not in the frame. State
- * that is marked as changed will then be send into a stream and the changed
- * status will be cleared, allowing data to only be sent/saved when actual
- * changes occur.
- * 
- * The provided SETTINGS type must support MsgPack and be copyable. An example
- * of settings is camera intrinsics.
- * 
- * COUNT is the number of settings channels available. For example, video state
- * has two settings channels, one for left camera and one for right camera.
- */
-template <typename SETTINGS, int COUNT>
-class FrameState {
-	public:
-	typedef SETTINGS Settings;
-
-	FrameState();
-	FrameState(FrameState &);
-	FrameState(FrameState &&);
-	~FrameState();
-
-	/**
-	 * Update the pose and mark as changed.
-	 */
-	void setPose(const Eigen::Matrix4d &pose) {
-		pose_ = pose;
-		changed_ += ftl::codecs::Channel::Pose;
-	}
-
-	/**
-	 * Update the left settings and mark as changed.
-	 */
-	void setLeft(const SETTINGS &p) {
-		static_assert(COUNT > 0, "No settings channel");
-		settings_[0] = p;
-		changed_ += ftl::codecs::Channel::Settings1;
-	}
-
-	/**
-	 * Update the right settings and mark as changed.
-	 */
-	void setRight(const SETTINGS &p) {
-		static_assert(COUNT > 1, "No second settings channel");
-		settings_[1] = p;
-		changed_ += ftl::codecs::Channel::Settings2;
-	}
-
-	/**
-	 * Change settings using ID number. Necessary when more than 2 settings
-	 * channels exist, otherwise use `setLeft` and `setRight`.
-	 */
-	template <int I>
-	void set(const SETTINGS &p) {
-		static_assert(I < COUNT, "Settings channel too large");
-		settings_[I] = p;
-		changed_ += __idToChannel(I);
-	}
-
-	/**
-	 * Get the current pose.
-	 */
-	inline const Eigen::Matrix4d &getPose() const { return pose_; }
-
-	/**
-	 * Get the left settings.
-	 */
-	inline const SETTINGS &getLeft() const { return settings_[0]; }
-
-	/**
-	 * Get the right settings.
-	 */
-	inline const SETTINGS &getRight() const { return settings_[1]; }
-
-	/**
-	 * Get a modifiable pose reference that does not change the changed status.
-	 * @attention Should only be used internally.
-	 * @todo Make private eventually.
-	 */
-	inline Eigen::Matrix4d &getPose() { return pose_; }
-
-	/**
-	 * Get a modifiable left settings reference that does not change
-	 * the changed status. Modifications made using this will not be propagated.
-	 * @attention Should only be used internally.
-	 * @todo Make private eventually.
-	 */
-	inline SETTINGS &getLeft() { return settings_[0]; }
-
-	/**
-	 * Get a modifiable right settings reference that does not change
-	 * the changed status. Modifications made using this will not be propagated.
-	 * @attention Should only be used internally.
-	 * @todo Make private eventually.
-	 */
-	inline SETTINGS &getRight() { return settings_[1]; }
-
-	/**
-	 * Get a named config property.
-	 */
-	template <typename T>
-	std::optional<T> get(const std::string &name) {
-		return ftl::config::getJSON<T>(config_, name);
-	}
-
-	/**
-	 * Helper class to specialising channel based state access.
-	 * @private
-	 */
-	template <typename T, ftl::codecs::Channel C, typename S, int N> struct As {
-		static const T &func(const ftl::data::FrameState<S,N> &t) {
-			throw FTL_Error("Type not supported for state channel");
-		}
-
-		static T &func(ftl::data::FrameState<S,N> &t) {
-			throw FTL_Error("Type not supported for state channel");
-		}
-	};
-
-	// Specialise for pose
-	template <typename S, int N>
-	struct As<Eigen::Matrix4d,ftl::codecs::Channel::Pose,S,N> {
-		static const Eigen::Matrix4d &func(const ftl::data::FrameState<S,N> &t) {
-			return t.pose_;
-		}
-
-		static Eigen::Matrix4d &func(ftl::data::FrameState<S,N> &t) {
-			return t.pose_;
-		}
-	};
-
-	// Specialise for settings 1
-	template <typename S, int N>
-	struct As<S,ftl::codecs::Channel::Settings1,S,N> {
-		static const S &func(const ftl::data::FrameState<S,N> &t) {
-			return t.settings_[0];
-		}
-
-		static S &func(ftl::data::FrameState<S,N> &t) {
-			return t.settings_[0];
-		}
-	};
-
-	// Specialise for settings 2
-	template <typename S, int N>
-	struct As<S,ftl::codecs::Channel::Settings2,S,N> {
-		static const S &func(const ftl::data::FrameState<S,N> &t) {
-			return t.settings_[1];
-		}
-
-		static S &func(ftl::data::FrameState<S,N> &t) {
-			return t.settings_[1];
-		}
-	};
-
-	// Specialise for config
-	template <typename S, int N>
-	struct As<nlohmann::json,ftl::codecs::Channel::Configuration,S,N> {
-		static const nlohmann::json &func(const ftl::data::FrameState<S,N> &t) {
-			return *t.config_;
-		}
-
-		static nlohmann::json &func(ftl::data::FrameState<S,N> &t) {
-			return *t.config_;
-		}
-	};
-
-	/**
-	 * Allow access to state items using a known channel number. By default
-	 * these throw an exception unless specialised to accept a particular type
-	 * for a particular channel. The specialisations are automatic for pose,
-	 * config and SETTINGS items.
-	 */
-	template <typename T, ftl::codecs::Channel C>
-	T &as() { return As<T,C,SETTINGS,COUNT>::func(*this); }
-
-	/**
-	 * Allow access to state items using a known channel number. By default
-	 * these throw an exception unless specialised to accept a particular type
-	 * for a particular channel. The specialisations are automatic for pose,
-	 * config and SETTINGS items.
-	 */
-	template <typename T, ftl::codecs::Channel C>
-	const T &as() const {
-		return As<T,C,SETTINGS,COUNT>::func(*this);
-	}
-
-	/**
-	 * Set a named config property. Also makes state as changed to be resent.
-	 */
-	template <typename T>
-	void set(const std::string &name, T value) {
-		ftl::config::setJSON<T>(config_, name, value);
-		changed_ += ftl::codecs::Channel::Configuration;
-	}
-
-	inline const nlohmann::json &getConfig() const { return *config_; }
-
-	inline nlohmann::json &getConfig() { return *config_; }
-
-	/**
-	 * Check if pose or settings have been modified and not yet forwarded.
-	 * Once forwarded through a pipeline / stream the changed status is cleared.
-	 */
-	inline bool hasChanged(ftl::codecs::Channel c) const { return changed_.has(c); }
-
-	/**
-	 * Copy assignment will clear the changed status of the original.
-	 */
-	FrameState &operator=(FrameState &);
-
-	FrameState &operator=(FrameState &&);
-
-	/**
-	 * Clear the changed status to unchanged.
-	 */
-	inline void clear() { changed_.clear(); }
-
-	private:
-	Eigen::Matrix4d pose_;
-	std::array<SETTINGS,COUNT> settings_;
-	nlohmann::json *config_;
-	ftl::codecs::Channels<64> changed_;  // Have the state channels changed?
-
-	static inline ftl::codecs::Channel __idToChannel(int id) {
-		return (id == 0) ? ftl::codecs::Channel::Settings1 : (id == 1) ?
-			ftl::codecs::Channel::Settings2 :
-			static_cast<ftl::codecs::Channel>(static_cast<int>(ftl::codecs::Channel::Settings3)+(id-2));
-	}
-};
-
-}
-}
-
-
-template <typename SETTINGS, int COUNT>
-ftl::data::FrameState<SETTINGS,COUNT>::FrameState() : settings_({{0}}), config_(ftl::config::createJSON()) {
-	pose_ = Eigen::Matrix4d::Identity();
-}
-
-template <typename SETTINGS, int COUNT>
-ftl::data::FrameState<SETTINGS,COUNT>::~FrameState() {
-	ftl::config::destroyJSON(config_);
-}
-
-template <typename SETTINGS, int COUNT>
-ftl::data::FrameState<SETTINGS,COUNT>::FrameState(ftl::data::FrameState<SETTINGS,COUNT> &f) {
-	pose_ = f.pose_;
-	settings_ = f.settings_;
-	changed_ = f.changed_;
-	ftl::config::copyJSON(config_, f.config_);
-	f.changed_.clear();
-}
-
-template <typename SETTINGS, int COUNT>
-ftl::data::FrameState<SETTINGS,COUNT>::FrameState(ftl::data::FrameState<SETTINGS,COUNT> &&f) {
-	pose_ = f.pose_;
-	settings_ = f.settings_;
-	changed_ = f.changed_;
-	config_ = f.config_;
-	f.config_ = nullptr;
-	f.changed_.clear();
-}
-
-template <typename SETTINGS, int COUNT>
-ftl::data::FrameState<SETTINGS,COUNT> &ftl::data::FrameState<SETTINGS,COUNT>::operator=(ftl::data::FrameState<SETTINGS,COUNT> &f) {
-	pose_ = f.pose_;
-	settings_ = f.settings_;
-	changed_ = f.changed_;
-	ftl::config::copyJSON(config_, f.config_);
-	f.changed_.clear();
-	return *this;
-}
-
-template <typename SETTINGS, int COUNT>
-ftl::data::FrameState<SETTINGS,COUNT> &ftl::data::FrameState<SETTINGS,COUNT>::operator=(ftl::data::FrameState<SETTINGS,COUNT> &&f) {
-	pose_ = f.pose_;
-	settings_ = f.settings_;
-	changed_ = f.changed_;
-	config_ = f.config_;
-	f.changed_.clear();
-	f.config_ = nullptr;
-	return *this;
-}
-
-#endif  // _FTL_DATA_FRAMESTATE_HPP_
diff --git a/components/structures/include/ftl/data/new_frame.hpp b/components/structures/include/ftl/data/new_frame.hpp
index 12628cc73c757f9171eda4b967c632482b2061b0..1be1544f3caf13cd12161cda4d6c38012d4ca1f8 100644
--- a/components/structures/include/ftl/data/new_frame.hpp
+++ b/components/structures/include/ftl/data/new_frame.hpp
@@ -26,12 +26,29 @@ class Session;
 class Pool;
 class FrameSet;
 
+/**
+ * Unique identifier for a single frame. This is stored as two 16bit numbers
+ * packed into a 32bit int. Every frame has a FrameID, as does every frameset.
+ * FrameID + Timestamp together will be a unique object within the system since
+ * frames cannot be duplicated.
+ */
 struct FrameID {
 	uint32_t id;
 
+	/**
+	 * Frameset ID for this frame.
+	 */
 	inline unsigned int frameset() const { return id >> 8; }
+
+	/**
+	 * Frame index within the frameset. This will correspond to the vector
+	 * index in the frameset object.
+	 */
 	inline unsigned int source() const { return id & 0xff; }
 
+	/**
+	 * The packed int with both frameset ID and index.
+	 */
 	operator uint32_t() const { return id; }
 
 	/**
@@ -43,21 +60,34 @@ struct FrameID {
 	FrameID() : id(0) {}
 };
 
+/**
+ * A frame object can be used in 3 different scenarios. A frame mode cannot be
+ * changed after construction and so each mode is constructed differently.
+ */
 enum FrameMode {
-	PRIMARY,
-	RESPONSE,
-	STANDALONE
+	PRIMARY,		/// A normal frame generated by a builder
+	RESPONSE,		/// A frame that acts as a reply to a primary frame
+	STANDALONE		/// Not associated with a source or stream, used for storage
 };
 
+/**
+ * The life cycle of a frame goes through all of these frame status stages.
+ * From the `Pool` it is created. After the frame is populated with initial data
+ * it is `stored`. New data is inserted to the frame before being `flushed`.
+ * Finally, when the frame is destroyed the data is transfered to the `Pool`
+ * for memory reuse and the frame is `released`.
+ */
 enum FrameStatus {
-	CREATED,   // Initial state, before store
-	STORED,    // Changed to this after call to `store`
-	FLUSHED,   // Changed to this after call to `flush`
-	RELEASED   // Destroyed or moved
+	CREATED,   /// Initial state, before store
+	STORED,    /// Changed to this after call to `store`
+	FLUSHED,   /// Changed to this after call to `flush`
+	RELEASED   /// Destroyed or moved
 };
 
 /**
- * Helper class to enable aggregation of aggregate channels.
+ * Helper class to enable aggregation of aggregate channels. Assignment acts to
+ * append data to a list rather than replace that data. It allows all data
+ * changes to be recorded. Not thread-safe however.
  */
 template <typename T>
 struct Aggregator {
@@ -90,6 +120,46 @@ struct Aggregator {
 	operator T() const { return list; }
 };
 
+/**
+ * A `Frame` is the primary unit of data within the system. A data source
+ * generates discrete blocks of data with a timestamp, these blocks are
+ * encapsulated in a frame that has any number of channels. A `Frame` must be
+ * constructed from a `Pool` object so that memory can be reused.
+ * 
+ * It can be moved around but not copied since the quantity of data involved in
+ * a frame is huge.
+ * 
+ * A frame does through the following stages:
+ *   1) Creation from reused memory in `Pool`
+ *   2) Populate with incoming initial data/changes (from stream)
+ *   3) Store of changes to persistent memory
+ *   4) Create any new data such as new video frames
+ *   5) Flush the data to transmit or save, becomes readonly
+ *   6) Release memory to `Pool`
+ * 
+ * A channel stores one particular element of data of a specified type. To write
+ * to a channel the `create` or `set` methods must be used, this will mark the
+ * channel as changed but can only occur before the frame is flushed and
+ * readonly. A `get` method allows const access to the data as long as the
+ * channel exists.
+ * 
+ * On change events are triggered when `store` occurs, whereas on flush events
+ * occur after flush. Both of these may occur on destruction if the frame was
+ * not stored or flushed before destruction.
+ * 
+ * Some channels may fail `hasChannel` but still be marked as `available`. This
+ * will be due to the data not being transmitted or encoded until requested.
+ * 
+ * Each frame is also associated with a `Session` object which stores all
+ * persistent data. Persistent data can then be accessed via any `Frame` with
+ * the same ID since they share a `Session`.
+ * 
+ * A `Frame` provides some basic methods, however, it can be cast to other
+ * frame types using the cast method which provides additional wrapper
+ * functions. An example is `ftl::rgbd::Frame`.
+ * 
+ * @see https://gitlab.utu.fi/nicolas.pope/ftl/-/wikis/Design/Frames
+ */
 class Frame {
 	friend class Session;
 	friend class ftl::data::Pool;
@@ -103,9 +173,26 @@ class Frame {
 
 	public:
 
+	/**
+	 * Millisecond timestamp when the frame was originally constructed and which
+	 * was the instant the data contents were captured.
+	 */
 	inline int64_t timestamp() const { return timestamp_; }
+
+	/**
+	 * Unique identification of data source. Combined with timestamp it will
+	 * become a unique item of data and a singleton in the system.
+	 */
 	inline FrameID id() const { return id_; }
+
+	/**
+	 * Access the frameset ID for this frame.
+	 */
 	inline unsigned int frameset() const { return id_.frameset(); }
+
+	/**
+	 * Access the index of the frame within the frameset.
+	 */
 	inline unsigned int source() const { return id_.source(); }
 
 	public:
@@ -126,16 +213,38 @@ class Frame {
 	Frame(const Frame &)=delete;
 	Frame &operator=(const Frame &)=delete;
 
+	/**
+	 * Obtain the current life-cycle status of the frame. This determines what
+	 * operations are permitted and what the behviour of the frame is.
+	 */
 	inline FrameStatus status() const { return status_; }
 
+	/**
+	 * Number of data channels in the frame. Excluding previous persistent data.
+	 */
 	inline size_t size() const { return data_.size(); }
 
+	/**
+	 * Is there data in this frame or in the persistent store for the given
+	 * channel number?
+	 */
 	bool has(ftl::codecs::Channel c) const;
 
+	/**
+	 * Check that either this frame or the persistent store has all the
+	 * channels in the set.
+	 */
 	bool hasAll(const std::unordered_set<ftl::codecs::Channel> &cs);
 
+	/**
+	 * @see has(Channel)
+	 */
 	inline bool hasChannel(ftl::codecs::Channel c) const { return has(c); }
 
+	/**
+	 * Does this frame have data for a given channel. This excludes any data
+	 * that may be in the persistent store.
+	 */
 	inline bool hasOwn(ftl::codecs::Channel c) const;
 
 	/**
@@ -145,62 +254,151 @@ class Frame {
 	 */
 	inline bool available(ftl::codecs::Channel c) const;
 
+	/**
+	 * A complete set of all channels that are potentially available but may
+	 * not currently have the data stored within this object. It means the
+	 * source of the frame can provide the data but has not be requested to
+	 * actually do so, or cannot due to resource constraints.
+	 */
 	std::unordered_set<ftl::codecs::Channel> available() const;
 
 	bool availableAll(const std::unordered_set<ftl::codecs::Channel> &cs) const;
 
 	/**
-	 * Used by a receiver to mark potential availability.
+	 * Used by a receiver to mark potential availability. Should not be used
+	 * elsewhere.
 	 */
 	inline void markAvailable(ftl::codecs::Channel c);
 
+	/**
+	 * Has a given channel been marked as changed?
+	 */
 	inline bool changed(ftl::codecs::Channel c) const;
 
+	/**
+	 * A channel is readonly if it has been flushed. An exception is thrown if
+	 * a write is attempted.
+	 */
 	inline bool readonly(ftl::codecs::Channel c) const;
 
+	/**
+	 * @see readonly(Channel)
+	 */
 	inline bool flushed(ftl::codecs::Channel c) const;
 
+	/**
+	 * Changes can occur from different sources for different reasons, this
+	 * obtains the cause of the change. For example, it can be a primary local
+	 * change or it can be received from a remote source. Change type does
+	 * influence behaviour during store and flush actions.
+	 */
 	inline ftl::data::ChangeType getChangeType(ftl::codecs::Channel c) const;
 
+	/**
+	 * Obtain the map of all changes.
+	 */
 	inline const std::unordered_map<ftl::codecs::Channel, ChangeType> &changed() const { return changed_; }
 
+	/**
+	 * Obtains a set of all available channels. This excludes channels in the
+	 * persistent store.
+	 */
 	std::unordered_set<ftl::codecs::Channel> channels() const;
 
+	/**
+	 * Test if the type of the channel matches the template type. Other
+	 * functions throw exceptions if wrong type is used, but this will not. It
+	 * will also return false if the channel is missing.
+	 */
 	template <typename T>
 	bool isType(ftl::codecs::Channel c) const;
 
+	/**
+	 * Get a readonly const reference to the content of a channel. If the
+	 * channel does not exist or if the template type does not match the content
+	 * then it throws an exception. The data can either be within this frame,
+	 * or if not in the frame then it checks the persistent store.
+	 * 
+	 * The data may internally still be encoded and will only be decoded on the
+	 * first call to `get`. It is therefore strongly advised that any initial
+	 * calls to `get` are not concurrent as it will not be thread-safe.
+	 */
 	template <typename T>
 	const T &get(ftl::codecs::Channel c) const;
 
+	/**
+	 * Should not be used directly, but allows direct access to the data for
+	 * a channel without any attempt to cast it to type. Throws exceptions if
+	 * the channel does not exist, but will also look in the persistent
+	 * store.
+	 */
 	const std::any &getAny(ftl::codecs::Channel c) const;
 
+	/**
+	 * Get the hash code from the C++ `type_info` structure that corresponds to
+	 * the current data contents.
+	 */
 	inline size_t type(ftl::codecs::Channel c) const { return getAny(c).type().hash_code(); }
 
+	/**
+	 * Should not be used. Allows modification without marking as changed.
+	 */
 	std::any &getAnyMutable(ftl::codecs::Channel c);
 
+	/**
+	 * Should not be used. Does not throw exceptions but can return a nullptr
+	 * instead if the channel does not exist or the type does not match.
+	 * Currently, this does not do lazy decode of data so may fail.
+	 */
 	template <typename T>
 	const T *getPtr(ftl::codecs::Channel c) const noexcept;
 
+	/**
+	 * Should not be used.
+	 */
 	template <typename T>
 	T *getMutable(ftl::codecs::Channel c);
 
+	/**
+	 * Mark a channel as changed even if there is no data. It can result in
+	 * `hasChannel` giving false but `changed` giving true. Intended to be used
+	 * internally.
+	 */
 	inline void touch(ftl::codecs::Channel c) {
 		markAvailable(c);
 		changed_[c] = (mode_ == FrameMode::PRIMARY) ? ChangeType::PRIMARY : ChangeType::RESPONSE;
 	}
 
+	/**
+	 * Should not be used.
+	 */
 	inline void touch(ftl::codecs::Channel c, ChangeType type) {
 		markAvailable(c);
 		changed_[c] = type;
 	}
 
+	/**
+	 * Mark the channel as unchanged. This will mean it will not be flushed,
+	 * transmitted or saved but will still return true with `hasChannel`.
+	 */
 	inline void untouch(ftl::codecs::Channel c) {
 		changed_.erase(c);
 	}
 
+	/**
+	 * Create a new channel with the given template type. It will mark the
+	 * channel as changed and return a mutable reference of the correct data
+	 * type. It is not possible to create a channel after it has been flushed,
+	 * this will throw an exception. The channel may have existing data from
+	 * the memory pool which can be overwritten, but this is not true for
+	 * every channel number (only video frames currently).
+	 */
 	template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0>
 	T &create(ftl::codecs::Channel c);
 
+	/**
+	 * Create method used for aggregate channels. @see create.
+	 */
 	template <typename T, std::enable_if_t<is_list<T>::value,int> = 0>
 	ftl::data::Aggregator<T> create(ftl::codecs::Channel c);
 
@@ -210,7 +408,7 @@ class Frame {
 	 * `persistent` this adds to session store instead of local frame store,
 	 * although the change status is added to the local frame.
 	 *
-	 * To be used by receive, no one else.
+	 * To be used by receiver, no one else. Currently unused.
 	 */
 	template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0>
 	T &createChange(ftl::codecs::Channel c, ftl::data::ChangeType t);
@@ -218,26 +416,59 @@ class Frame {
 	template <typename T, std::enable_if_t<is_list<T>::value,int> = 0>
 	ftl::data::Aggregator<T> createChange(ftl::codecs::Channel c, ftl::data::ChangeType t);
 
+	/**
+	 * Create a change but with encoded data provided. This allows for both
+	 * lazy decode and for subsequent data forwarding without encoding.
+	 * 
+	 * Currently unused.
+	 */
 	template <typename T>
 	T &createChange(ftl::codecs::Channel c, ftl::data::ChangeType t, const ftl::codecs::Packet &data);
 
+	/**
+	 * Create a channel, mark with the given change type and provided encoded
+	 * data. Does not decode the data as it does not know the actually data
+	 * type of this channel at this time.
+	 * 
+	 * To be used by `receiver`.
+	 * @see ftl::stream::Receiver
+	 */
 	inline void informChange(ftl::codecs::Channel c, ftl::data::ChangeType t, const ftl::codecs::Packet &data) {
 		createAnyChange(c, t, data);
 	}
 
+	/**
+	 * Create a channel, mark with a given change type but do not provide any
+	 * data or type information.
+	 */
 	inline void informChange(ftl::codecs::Channel c, ftl::data::ChangeType t) {
 		createAnyChange(c, t);
 	}
 
+	/**
+	 * Create a channel, mark with a given change type and provided unencoded
+	 * data. The data is moved into the channel. This is used by `Receiver` to
+	 * provide a loopback functionality.
+	 */
 	inline void informChange(ftl::codecs::Channel c, ftl::data::ChangeType t, std::any &data) {
 		createAnyChange(c, t) = std::move(data);
 	}
 
+	/**
+	 * Retrieve all encoded data packets for a channel, if any. Note that
+	 * encoded data is removed if the channel is modified.
+	 */
 	const std::list<ftl::codecs::Packet> &getEncoded(ftl::codecs::Channel c) const;
 
+	/** Do not use. */
 	template <typename T, typename ...ARGS>
 	T &emplace(ftl::codecs::Channel, ARGS...);
 
+	/**
+	 * Can be used instead of `create` to modify channel contents. It has the
+	 * same rules as `create`, except that if the channel does not exist then
+	 * it will throw an exception instead of creating the channel.
+	 */
 	template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0>
 	T &set(ftl::codecs::Channel c);
 
@@ -254,10 +485,29 @@ class Frame {
 	 */
 	void hardRemove(ftl::codecs::Channel);
 
+	/**
+	 * Add a callback to a channel to watch for change events. These are
+	 * triggered by the `store` operation. Note that `Receiver` will call
+	 * `store` on a frame before generating a frameset callback, therefore
+	 * these events always occur and complete before the frameset is generated.
+	 */
 	inline ftl::Handle onChange(ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb);
 
+	/**
+	 * Add a callback to listen for any and all changes to the frame.
+	 * @see onChange(Channel, cb).
+	 */
 	inline ftl::Handle onChange(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb);
 
+	/**
+	 * All changed channels generate a flush event when the frame is flushed
+	 * explicitly or on destruction. There is one exception, forwarded changes
+	 * do generate a change event but do no subsequently generate a flush event
+	 * as they are considered completed changes. This prevents loops whilst
+	 * ensuring everyone has a copy of the change.
+	 * 
+	 * @see changeType
+	 */
 	inline ftl::Handle onFlush(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb);
 
 	/**
@@ -296,9 +546,19 @@ class Frame {
 	 */
 	void release();
 
-	/** Send changes back through origin stream. */
+	/**
+	 * Send changes back through origin stream. Causes all channels to be
+	 * individually flushed, resulting in flush events and each channel being
+	 * readonly. Only changed channels are flushed. Note: A frame cannot be
+	 * flushed multiple times and the entire frame becomes readonly after this.
+	 */
 	bool flush();
 
+	/**
+	 * Force a flush of only a single channel, allowing the frame to continue
+	 * to be modified (except this channel). This will generate a single
+	 * flush event.
+	 */
 	bool flush(ftl::codecs::Channel c);
 
 	/** Copy persistent changes to session. To be called before dispatch. */
@@ -306,24 +566,31 @@ class Frame {
 
 	/**
 	 * Should only be used by Feed class. Ignores storage rules and saves
-	 * to session anyway.
+	 * to session anyway. Unused.
 	 */
 	void forceStore();
 
+	/**
+	 * Iterator.
+	 */
 	inline auto begin() const { return data_.begin(); }
 	inline auto end() const { return data_.end(); }
 
-	// onBeginFlush
-	// onEndFlush
-	// onError
-
 	inline MUTEX &mutex();
 
 	/**
-	 * Generate a new frame to respond to this one.
+	 * Generate a new frame to respond to this one. The destruction of this
+	 * new frame will flush the changes and results in those response changes
+	 * being transmitted back to the original source of the frame. The original
+	 * source will then see these changes in the next frame it attempt to
+	 * generate.
 	 */
 	Frame response();
 
+	/**
+	 * Convert this frame to another type. That type must not have any
+	 * additional member variables, only wrapper methods.
+	 */
 	template <typename T>
 	T &cast();
 
@@ -336,8 +603,17 @@ class Frame {
 	 */
 	static Frame make_standalone();
 
+	/**
+	 * The memory pool associated with this frame. Note: the pool class also
+	 * provides `onFlush` events, allowing an event handler to respond to any
+	 * frame that is flushed.
+	 */
 	inline Pool *pool() const { return pool_; }
 
+	/**
+	 * The persistent data store for this frame. It is also a frame object and
+	 * can be used in the same manner.
+	 */
 	inline Session *parent() const { return parent_; }
 
 	inline FrameMode mode() const { return mode_; }