From 3c39f256bcb2b4140d66609b7442261407f32ec4 Mon Sep 17 00:00:00 2001
From: Nicolas Pope <nicolas.pope@utu.fi>
Date: Thu, 30 Jul 2020 10:20:50 +0300
Subject: [PATCH] Audio mixer GUI and more vectorisation

---
 applications/gui2/src/modules/camera.cpp      | 13 +++++
 applications/gui2/src/modules/camera.hpp      |  3 +
 applications/gui2/src/views/camera.cpp        |  2 +-
 applications/gui2/src/widgets/soundctrl.cpp   | 40 +++++++++++++-
 applications/gui2/src/widgets/soundctrl.hpp   |  5 +-
 components/audio/include/ftl/audio/buffer.hpp | 26 ++++++++-
 components/audio/include/ftl/audio/mixer.hpp  | 55 ++++++++++++-------
 components/audio/src/speaker.cpp              | 12 +---
 components/audio/test/mixer_unit.cpp          | 16 ++++--
 .../streams/include/ftl/streams/feed.hpp      | 12 ++++
 .../streams/include/ftl/streams/renderer.hpp  |  3 +
 components/streams/src/baserender.hpp         |  4 ++
 components/streams/src/feed.cpp               |  9 +++
 components/streams/src/renderer.cpp           |  5 ++
 .../streams/src/renderers/openvr_render.cpp   |  5 +-
 .../streams/src/renderers/openvr_render.hpp   |  2 -
 .../streams/src/renderers/screen_render.cpp   |  5 +-
 .../streams/src/renderers/screen_render.hpp   |  3 -
 18 files changed, 169 insertions(+), 51 deletions(-)

diff --git a/applications/gui2/src/modules/camera.cpp b/applications/gui2/src/modules/camera.cpp
index a47d35f1a..c362d6a48 100644
--- a/applications/gui2/src/modules/camera.cpp
+++ b/applications/gui2/src/modules/camera.cpp
@@ -3,6 +3,7 @@
 
 #include "../views/camera3d.hpp"
 #include <ftl/rgbd/capabilities.hpp>
+#include <ftl/streams/renderer.hpp>
 #include <chrono>
 
 #include <opencv2/imgproc.hpp>
@@ -330,6 +331,18 @@ std::string Camera::getActiveSourceURI() {
 	return "";
 }
 
+ftl::audio::StereoMixerF<100> *Camera::mixer() {
+	if (mixer_) return mixer_;
+	if (movable_) {
+		auto *rend = io->feed()->getRenderer(frame_id_);
+		if (rend) {
+			mixer_ = &(rend->mixer());
+			return mixer_;
+		}
+	}
+	return nullptr;
+}
+
 bool Camera::isRecording() {
 	return io->feed()->isRecording();
 }
diff --git a/applications/gui2/src/modules/camera.hpp b/applications/gui2/src/modules/camera.hpp
index d4da36f3b..bdcb6dcaa 100644
--- a/applications/gui2/src/modules/camera.hpp
+++ b/applications/gui2/src/modules/camera.hpp
@@ -7,6 +7,7 @@
 #include <ftl/render/colouriser.hpp>
 #include <ftl/render/overlay.hpp>
 #include <ftl/codecs/touch.hpp>
+#include <ftl/audio/mixer.hpp>
 
 namespace ftl {
 namespace gui2 {
@@ -49,6 +50,7 @@ public:
 
 	ftl::render::Colouriser* colouriser() { return colouriser_.get(); };
 	ftl::overlay::Overlay* overlay() { return overlay_.get(); }
+	ftl::audio::StereoMixerF<100> *mixer();
 
 	void drawOverlay(NVGcontext *ctx);
 
@@ -90,6 +92,7 @@ private:
 	std::map<ftl::data::Message,std::string> messages_;
 
 	CameraView* view = nullptr;
+	ftl::audio::StereoMixerF<100> *mixer_ = nullptr;
 
 	MUTEX mtx_;
 
diff --git a/applications/gui2/src/views/camera.cpp b/applications/gui2/src/views/camera.cpp
index 565381ebc..76ff8efab 100644
--- a/applications/gui2/src/views/camera.cpp
+++ b/applications/gui2/src/views/camera.cpp
@@ -204,7 +204,7 @@ MediaPanel::MediaPanel(nanogui::Widget *parent, ftl::gui2::Camera* ctrl) :
 	this->setTheme(theme);
 
 	// Volume control
-	button_volume = new ftl::gui2::VolumeButton(this);
+	button_volume = new ftl::gui2::VolumeButton(this, ctrl_->mixer());
 	button_volume->setValue(ctrl_->volume());
 	button_volume->setCallback([ctrl = ctrl_](float v){ ctrl->setVolume(v); });
 
diff --git a/applications/gui2/src/widgets/soundctrl.cpp b/applications/gui2/src/widgets/soundctrl.cpp
index 87ca183a5..4a8d6a08d 100644
--- a/applications/gui2/src/widgets/soundctrl.cpp
+++ b/applications/gui2/src/widgets/soundctrl.cpp
@@ -9,8 +9,8 @@ using ftl::gui2::PopupButton;
 using ftl::gui2::VolumeButton;
 using ftl::gui2::Screen;
 
-VolumeButton::VolumeButton(nanogui::Widget *parent) :
-	ftl::gui2::PopupButton(parent, "", ENTYPO_ICON_SOUND) {
+VolumeButton::VolumeButton(nanogui::Widget *parent, ftl::audio::StereoMixerF<100> *mixer) :
+	ftl::gui2::PopupButton(parent, "", ENTYPO_ICON_SOUND), mixer_(mixer) {
 	setChevronIcon(-1);
 
 	muted_ = false;
@@ -27,6 +27,42 @@ VolumeButton::VolumeButton(nanogui::Widget *parent) :
 		setValue(value);
 		if (cb_) { cb_(value); }
 	});
+
+	if (mixer) {
+		auto *mixbut = new nanogui::Button(mPopup, "Mixer", ENTYPO_ICON_SOUND_MIX);
+		mPopup->setAnchorHeight(70);
+
+		auto *mixer_widget = new nanogui::Widget(mPopup);
+		mixer_widget->setLayout(new nanogui::GroupLayout(0, 6, 14, 0));
+		mixer_widget->setVisible(false);
+
+		// Add mixer slider for each track in mixer.
+		for (int t=0; t<mixer->tracks(); ++t) {
+			auto *label = new nanogui::Label(mixer_widget, mixer->name(t));
+			label->setFontSize(12);
+			auto *mixslider = new nanogui::Slider(mixer_widget);
+			mixslider->setHighlightColor(dynamic_cast<Screen*>(screen())->getColor("highlight1"));
+			mixslider->setHeight(20);
+			mixslider->setValue(mixer->gain(t));
+			mixslider->setHighlightedRange({0.0f, mixer->gain(t)});
+
+			mixslider->setCallback([this,t,mixslider](float value) {
+				mixslider->setValue(value);
+				mixslider->setHighlightedRange({0.0f, value});
+				mixer_->setGain(t, value);
+			});
+		}
+
+		mixbut->setCallback([this,mixer_widget]() {
+			mixer_widget->setVisible(!mixer_widget->visible());
+			if (mixer_widget->visible()) {
+				mPopup->setAnchorHeight(70+mixer_widget->childCount()*20);
+			} else {
+				mPopup->setAnchorHeight(70);
+			}
+			screen()->performLayout();
+		});
+	}
 }
 
 VolumeButton::~VolumeButton() {
diff --git a/applications/gui2/src/widgets/soundctrl.hpp b/applications/gui2/src/widgets/soundctrl.hpp
index 1db40ec5c..5495edd5c 100644
--- a/applications/gui2/src/widgets/soundctrl.hpp
+++ b/applications/gui2/src/widgets/soundctrl.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <nanogui/entypo.h>
+#include <ftl/audio/mixer.hpp>
 
 #include "popupbutton.hpp"
 
@@ -9,7 +10,7 @@ namespace gui2 {
 
 class VolumeButton : public ftl::gui2::PopupButton {
 public:
-	VolumeButton(nanogui::Widget *parent);
+	VolumeButton(nanogui::Widget *parent, ftl::audio::StereoMixerF<100> *mixer);
 	virtual ~VolumeButton();
 
 	// callback, new value passed in argument
@@ -38,6 +39,8 @@ private:
 	nanogui::Slider* slider_;
 	std::function<void(float)> cb_;
 
+	ftl::audio::StereoMixerF<100> *mixer_;
+
 	float scroll_step_ = 0.02f;
 	float value_;
 	bool muted_;
diff --git a/components/audio/include/ftl/audio/buffer.hpp b/components/audio/include/ftl/audio/buffer.hpp
index 545e15bf1..e3001d63d 100644
--- a/components/audio/include/ftl/audio/buffer.hpp
+++ b/components/audio/include/ftl/audio/buffer.hpp
@@ -3,6 +3,7 @@
 
 #include <vector>
 #include <cmath>
+#include <Eigen/Eigen>
 
 //#define LOGURU_REPLACE_GLOG 1
 //#include <loguru.hpp>
@@ -36,6 +37,9 @@ class Buffer {
 
 	float delay() const { return cur_delay_ / static_cast<float>(rate_); }
 
+	inline void setGain(float g) { gain_ = g; }
+	inline float gain() const { return gain_; }
+
 	virtual void reset() {
 		cur_delay_ = req_delay_;
 	}
@@ -49,6 +53,7 @@ class Buffer {
 	float req_delay_;
 	int channels_;
 	int frame_size_;
+	float gain_ = 1.0f;
 };
 
 //static constexpr int kBufferCount = 100;
@@ -78,12 +83,27 @@ class FixedBuffer : public ftl::audio::Buffer<T> {
 	}
 
 	inline void readFrame(T *d) {
-		T *out = d;
+		T* __restrict out = d;
+		//if ((size_t(out) & 0x1f) == 0) out_alignment_ = 32;
+		//else if ((size_t(out) & 0xf) == 0) out_alignment_ = 16;
+		//else if ((size_t(out) & 0x7) == 0) out_alignment_ = 8;
+
 		if (read_position_ < 0 || read_position_ >= write_position_-1) {
 			for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = 0;
 		} else {
-			T *in = &data_[(read_position_++) % SIZE][0];
-			for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = *in++;
+			const T* __restrict in = data_[(read_position_++) % SIZE];
+
+			// 16 byte aligned, use SIMD intrinsics
+			if ((size_t(out) & 0xf) == 0) {
+				for (size_t i=0; i<CHAN*FRAME; i += 4) {
+					Eigen::Map<Eigen::Matrix<float,4,1>,Eigen::Aligned16> vout(out+i);
+					const Eigen::Map<const Eigen::Matrix<float,4,1>,Eigen::Aligned16> vin(in+i);
+					vout = vin*this->gain_;
+				}
+			// Not aligned
+			} else {
+				for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = this->gain_ * (*in++);
+			}
 		}
 	}
 
diff --git a/components/audio/include/ftl/audio/mixer.hpp b/components/audio/include/ftl/audio/mixer.hpp
index b8e41d16e..5d496217a 100644
--- a/components/audio/include/ftl/audio/mixer.hpp
+++ b/components/audio/include/ftl/audio/mixer.hpp
@@ -21,20 +21,19 @@ namespace audio {
 template <typename T, int CHAN, int FRAME, int SIZE>
 class FixedMixer : public ftl::audio::Buffer<T> {
 	public:
-	FixedMixer() : Buffer<T>(CHAN, FRAME, 44100), write_position_(0), read_position_(0), offset_(0) { resize(1); }
-	explicit FixedMixer(int tracks) : Buffer<T>(CHAN, FRAME, 44100), write_position_(0), read_position_(0), offset_(0) { resize(tracks); }
-	FixedMixer(int tracks, int rate) : Buffer<T>(CHAN, FRAME, rate), write_position_(0), read_position_(0), offset_(0) { resize(tracks); }
+	FixedMixer() : Buffer<T>(CHAN, FRAME, 44100) { }
+	explicit FixedMixer(int rate) : Buffer<T>(CHAN, FRAME, rate) { }
 
 
 	inline int maxFrames() const { return SIZE; }
 
 	inline void readFrame(T *d) {
-		T *out = d;
+		T* __restrict out = d;
 		if (read_position_ >= write_position_) {
-			for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = 0;
+			std::fill(out, out+CHAN*FRAME, T(0));
 		} else {
-			T *in = data_[(read_position_++) % SIZE];
-			for (size_t i=0; i<CHAN*FRAME; ++i) *out++ = *in++;
+			const T* __restrict in = data_[(read_position_++) % SIZE];
+			std::copy(in, in+CHAN*FRAME, out);
 		}
 	}
 
@@ -49,7 +48,7 @@ class FixedMixer : public ftl::audio::Buffer<T> {
 	void write(const std::vector<T> &in) override;
 
 	inline void write(int track, const std::vector<T> &in) {
-		tracks_[track].write(in);
+		tracks_.at(track).write(in);
 	}
 
 	void mix();
@@ -58,7 +57,7 @@ class FixedMixer : public ftl::audio::Buffer<T> {
 
 	void reset() override {
 		Buffer<T>::reset();
-		write_position_ = 0; //int(this->cur_delay_);
+		write_position_ = 0;
 		read_position_ = 0;
 	}
 
@@ -66,22 +65,25 @@ class FixedMixer : public ftl::audio::Buffer<T> {
 	inline int readPosition() const { return read_position_; }
 	inline int tracks() const { return track_num_; }
 
-	inline void setDelay(int track, float d) { tracks_[track].setDelay(d); }
-	inline float delay(int track) const { return tracks_[track].delay(); }
+	inline void setDelay(int track, float d) { tracks_.at(track).setDelay(d); }
+	inline float delay(int track) const { return tracks_.at(track).delay(); }
 
-	inline void setGain(float g) { gain_ = g; }
-	inline float gain() const { return gain_; }
+	inline void setGain(int track, float g) { tracks_.at(track).setGain(g); }
+	inline float gain(int track) const { return tracks_.at(track).gain(); }
 
-	void resize(int tracks);
+	//void resize(int tracks);
+
+	int add(const std::string &name);
+
+	const std::string &name(int track) const { return names_.at(track); }
 
 	private:
 	int track_num_=0;
-	int write_position_;
-	int read_position_;
-	int offset_;
-	float gain_ = 1.0f;
+	int write_position_=0;
+	int read_position_=0;
 	alignas(32) T data_[SIZE][CHAN*FRAME];
 	std::vector<ftl::audio::FixedBuffer<T,CHAN,FRAME,SIZE>> tracks_;
+	std::vector<std::string> names_;
 };
 
 // ==== Implementations ========================================================
@@ -93,6 +95,8 @@ void FixedMixer<T,CHAN,FRAME,SIZE>::write(const std::vector<T> &in) {
 
 template <typename T, int CHAN, int FRAME, int SIZE>
 void FixedMixer<T,CHAN,FRAME,SIZE>::mix() {
+	if (track_num_ == 0) return;
+
 	// Add together up to most recent frame
 	int min_write = std::numeric_limits<int>::max();
 	for (auto &t : tracks_) {
@@ -112,10 +116,10 @@ void FixedMixer<T,CHAN,FRAME,SIZE>::mix() {
 			// For each track, accumulate the samples
 			for (auto &t : tracks_) {
 				const Eigen::Map<Eigen::Matrix<float,8,1>,Eigen::Aligned32> v2(&t.data(wp)[i]);
-				v1 += v2;
+				v1 += t.gain()*v2;
 			}
 
-			v1 *= gain_;
+			v1 *= this->gain_;
 		}
 
 		++write_position_;
@@ -127,12 +131,13 @@ void FixedMixer<T,CHAN,FRAME,SIZE>::read(std::vector<T> &out, int count) {
 	out.resize(FRAME*count*CHAN);
 	T *ptr = out.data();
 	for (int i=0; i<count; ++i) {
+		// TODO: Do mix here directly
 		readFrame(ptr);
 		ptr += FRAME*CHAN;
 	}
 }
 
-template <typename T, int CHAN, int FRAME, int SIZE>
+/*template <typename T, int CHAN, int FRAME, int SIZE>
 void FixedMixer<T,CHAN,FRAME,SIZE>::resize(int t) {
 	if (track_num_ == t) return;
 	
@@ -142,6 +147,14 @@ void FixedMixer<T,CHAN,FRAME,SIZE>::resize(int t) {
 		auto &tr = tracks_.emplace_back();
 		tr.setWritePosition(write_position_);
 	}
+}*/
+
+template <typename T, int CHAN, int FRAME, int SIZE>
+int FixedMixer<T,CHAN,FRAME,SIZE>::add(const std::string &name) {
+	names_.push_back(name);
+	auto &tr = tracks_.emplace_back();
+	tr.setWritePosition(write_position_);
+	return track_num_++;
 }
 
 // ==== Common forms ===========================================================
diff --git a/components/audio/src/speaker.cpp b/components/audio/src/speaker.cpp
index 4c17df9ec..7db2e8426 100644
--- a/components/audio/src/speaker.cpp
+++ b/components/audio/src/speaker.cpp
@@ -137,16 +137,7 @@ void Speaker::queue(int64_t ts, ftl::audio::Frame &frame) {
 
 	//LOG(INFO) << "Buffer Fullness (" << ts << "): " << buffer_->size() << " - " << audio.size();
 	for (const auto &d : audio) {
-		if (volume_ != 1.0) {
-			auto data = d.data();
-			for (auto &v : data) {
-				v = v * volume_;
-			}
-			buffer_->write(data);
-		}
-		else {
-			buffer_->write(d.data());
-		}
+		buffer_->write(d.data());
 	}
 	//LOG(INFO) << "Audio delay: " << buffer_.delay() << "s";
 }
@@ -164,6 +155,7 @@ void Speaker::setDelay(int64_t ms) {
 void Speaker::setVolume(float value) {
 	// TODO: adjust volume using system mixer
 	volume_ = std::max(0.0f, std::min(1.0f, value));
+	if (buffer_) buffer_->setGain(volume_);
 }
 
 float Speaker::volume() {
diff --git a/components/audio/test/mixer_unit.cpp b/components/audio/test/mixer_unit.cpp
index 233914446..b32b9cdb4 100644
--- a/components/audio/test/mixer_unit.cpp
+++ b/components/audio/test/mixer_unit.cpp
@@ -5,7 +5,10 @@ using ftl::audio::StereoMixerF;
 
 TEST_CASE("Audio Mixer Stereo Float", "") {
 	SECTION("Add two in sync tracks") {
-		StereoMixerF<100> mixer(2);
+		StereoMixerF<100> mixer;
+
+		mixer.add("Track1");
+		mixer.add("Track2");
 
 		// Three 960 sample stereo frames
 		std::vector<float> in1(960*2*3);
@@ -36,7 +39,10 @@ TEST_CASE("Audio Mixer Stereo Float", "") {
 	}
 
 	SECTION("Add two out of sync tracks") {
-		StereoMixerF<100> mixer(2);
+		StereoMixerF<100> mixer;
+
+		mixer.add("Track1");
+		mixer.add("Track2");
 
 		// Three 960 sample stereo frames
 		std::vector<float> in1(960*2*3);
@@ -89,7 +95,9 @@ TEST_CASE("Audio Mixer Stereo Float", "") {
 
 TEST_CASE("Audio Mixer Stereo Float Dynamic Tracks", "") {
 	SECTION("Add one track after write") {
-		StereoMixerF<100> mixer(1);
+		StereoMixerF<100> mixer;
+
+		mixer.add("Track1");
 
 		// Three 960 sample stereo frames
 		std::vector<float> in1(960*2*3);
@@ -104,7 +112,7 @@ TEST_CASE("Audio Mixer Stereo Float Dynamic Tracks", "") {
 		std::vector<float> in2(960*2*3);
 		for (int i=0; i<960*2*3; ++i) in2[i] = float(i)+2.0f;
 
-		mixer.resize(2);
+		mixer.add("Track2");
 		mixer.write(0, in1);
 		mixer.write(1, in2);
 		mixer.mix();
diff --git a/components/streams/include/ftl/streams/feed.hpp b/components/streams/include/ftl/streams/feed.hpp
index 53bdffa23..62234701b 100644
--- a/components/streams/include/ftl/streams/feed.hpp
+++ b/components/streams/include/ftl/streams/feed.hpp
@@ -98,6 +98,18 @@ public:
 	 */
 	std::string getSourceURI(ftl::data::FrameID id);
 
+	/**
+	 * Get the renderer used to generate a frameset. This will return a nullptr
+	 * if the frameset is not from a local renderer.
+	 */
+	ftl::render::Source *getRenderer(ftl::data::FrameID id);
+
+	/**
+	 * Get the RGB-Depth source for a frame, if the source is a local device.
+	 * It will return a nullptr if there is no local source object.
+	 */
+	ftl::rgbd::Source *getRGBD(ftl::data::FrameID id);
+
 	void remove(const std::string &str);
 	void remove(uint32_t id);
 
diff --git a/components/streams/include/ftl/streams/renderer.hpp b/components/streams/include/ftl/streams/renderer.hpp
index c922fe20d..441fd32ce 100644
--- a/components/streams/include/ftl/streams/renderer.hpp
+++ b/components/streams/include/ftl/streams/renderer.hpp
@@ -6,6 +6,7 @@
 #include <ftl/render/renderer.hpp>
 #include <ftl/render/CUDARender.hpp>
 #include <ftl/streams/feed.hpp>
+#include <ftl/audio/mixer.hpp>
 
 namespace ftl {
 namespace render {
@@ -28,6 +29,8 @@ class Source : public ftl::Configurable, public ftl::data::DiscreteSource {
 
 	static bool supports(const std::string &uri);
 
+	ftl::audio::StereoMixerF<100> &mixer();
+
 	private:
 	ftl::stream::Feed *feed_;
 	ftl::render::BaseSourceImpl *impl_;
diff --git a/components/streams/src/baserender.hpp b/components/streams/src/baserender.hpp
index b8d3e7093..51c6ce09c 100644
--- a/components/streams/src/baserender.hpp
+++ b/components/streams/src/baserender.hpp
@@ -5,6 +5,7 @@
 #include <ftl/cuda_util.hpp>
 #include <ftl/rgbd/camera.hpp>
 #include <ftl/rgbd/frame.hpp>
+#include <ftl/audio/mixer.hpp>
 
 namespace ftl{
 namespace render {
@@ -27,8 +28,11 @@ class BaseSourceImpl {
 
 	ftl::render::Source *host() { return host_; }
 
+	inline ftl::audio::StereoMixerF<100> &mixer() { return mixer_; }
+
 	protected:
 	ftl::render::Source *host_;
+	ftl::audio::StereoMixerF<100> mixer_;
 };
 
 }
diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp
index b6c15f2b9..5f71e7fcf 100644
--- a/components/streams/src/feed.cpp
+++ b/components/streams/src/feed.cpp
@@ -966,6 +966,15 @@ void Feed::render() {
 	}
 }
 
+ftl::render::Source *Feed::getRenderer(ftl::data::FrameID id) {
+	auto i = renderers_.find(id.frameset());
+	if (i != renderers_.end()) {
+		return i->second;
+	} else {
+		return nullptr;
+	}
+}
+
 uint32_t Feed::getID(const std::string &source) {
 	return fsid_lookup_.at(source);
 }
diff --git a/components/streams/src/renderer.cpp b/components/streams/src/renderer.cpp
index 5da8355ed..439ea2f2e 100644
--- a/components/streams/src/renderer.cpp
+++ b/components/streams/src/renderer.cpp
@@ -27,6 +27,11 @@ Source::~Source() {
 	if (impl_) delete impl_;
 }
 
+ftl::audio::StereoMixerF<100> &Source::mixer() {
+	if (!impl_) throw FTL_Error("No implementation");
+	return impl_->mixer();
+}
+
 bool Source::supports(const std::string &puri) {
 	ftl::URI uri(puri);
 	if (!uri.isValid() || uri.getScheme() != ftl::URI::SCHEME_DEVICE) return false;
diff --git a/components/streams/src/renderers/openvr_render.cpp b/components/streams/src/renderers/openvr_render.cpp
index 0f578740e..e5ac8c0f8 100644
--- a/components/streams/src/renderers/openvr_render.cpp
+++ b/components/streams/src/renderers/openvr_render.cpp
@@ -387,8 +387,9 @@ bool OpenVRRender::retrieve(ftl::data::Frame &frame_out) {
 					if (f.hasChannel(Channel::AudioStereo)) {
 						// Map a mixer track to this frame
 						auto &mixmap = mixmap_[f.id().id];
-						if (mixmap.track == -1) mixmap.track = tracks_++;
-						mixer_.resize(tracks_);
+						if (mixmap.track == -1) {
+							mixmap.track = mixer_.add(f.name());
+						}
 
 						// Do mix but must not mix same frame multiple times
 						if (mixmap.last_timestamp != f.timestamp()) {
diff --git a/components/streams/src/renderers/openvr_render.hpp b/components/streams/src/renderers/openvr_render.hpp
index 1423dd8dc..9accd58a4 100644
--- a/components/streams/src/renderers/openvr_render.hpp
+++ b/components/streams/src/renderers/openvr_render.hpp
@@ -61,8 +61,6 @@ class OpenVRRender : public ftl::render::BaseSourceImpl {
 		int track=-1;
 	};
 
-	int tracks_=0;
-	ftl::audio::StereoMixerF<100> mixer_;
 	std::unordered_map<uint32_t, AudioMixerMapping> mixmap_;
 
 	bool initVR();
diff --git a/components/streams/src/renderers/screen_render.cpp b/components/streams/src/renderers/screen_render.cpp
index a125f770c..36da28dac 100644
--- a/components/streams/src/renderers/screen_render.cpp
+++ b/components/streams/src/renderers/screen_render.cpp
@@ -163,8 +163,9 @@ bool ScreenRender::retrieve(ftl::data::Frame &frame_out) {
 					if (f.hasChannel(Channel::AudioStereo)) {
 						// Map a mixer track to this frame
 						auto &mixmap = mixmap_[f.id().id];
-						if (mixmap.track == -1) mixmap.track = tracks_++;
-						mixer_.resize(tracks_);
+						if (mixmap.track == -1) {
+							mixmap.track = mixer_.add(f.name());
+						}
 
 						// Do mix but must not mix same frame multiple times
 						if (mixmap.last_timestamp != f.timestamp()) {
diff --git a/components/streams/src/renderers/screen_render.hpp b/components/streams/src/renderers/screen_render.hpp
index a24bb7476..91a867cf0 100644
--- a/components/streams/src/renderers/screen_render.hpp
+++ b/components/streams/src/renderers/screen_render.hpp
@@ -6,7 +6,6 @@
 #include <ftl/render/renderer.hpp>
 #include <ftl/render/CUDARender.hpp>
 #include <ftl/streams/feed.hpp>
-#include <ftl/audio/mixer.hpp>
 
 #include "../baserender.hpp"
 
@@ -42,8 +41,6 @@ class ScreenRender : public ftl::render::BaseSourceImpl {
 		int track=-1;
 	};
 
-	int tracks_=0;
-	ftl::audio::StereoMixerF<100> mixer_;
 	std::unordered_map<uint32_t, AudioMixerMapping> mixmap_;
 };
 
-- 
GitLab