diff foosdk/sdk/foobar2000/SDK/dsp.h @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/SDK/dsp.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,737 @@
+#pragma once
+#include "audio_chunk.h"
+#include "filesystem.h"
+
+#ifdef FOOBAR2000_HAVE_DSP
+
+#ifdef FOOBAR2000_MOBILE
+#include "dsp-context.h"
+#endif
+
+#include <memory>
+#include <vector>
+
+class dsp_preset; class dsp_chain_config; // forward declaration
+
+#ifdef FOOBAR2000_HAVE_METADB
+typedef metadb_handle_ptr dsp_track_t;
+#else
+typedef trackRef dsp_track_t;
+#endif
+
+//! Interface to a DSP chunk list. A DSP chunk list object is passed to the DSP chain each time, since DSPs are allowed to remove processed chunks or insert new ones.
+class NOVTABLE dsp_chunk_list {
+public:
+	virtual t_size get_count() const = 0;
+	virtual audio_chunk * get_item(t_size n) const = 0;
+	virtual void remove_by_idx(t_size idx) = 0;
+	virtual void remove_mask(const bit_array & mask) = 0;
+	virtual audio_chunk * insert_item(t_size idx,t_size hint_size=0) = 0;
+
+	audio_chunk * add_item(t_size hint_size=0);
+
+	void remove_all();
+
+	double get_duration();
+
+	void add_chunk(const audio_chunk * chunk);
+
+	void remove_bad_chunks();
+protected:
+	dsp_chunk_list() {}
+	~dsp_chunk_list() {}
+};
+
+class dsp_chunk_list_impl : public dsp_chunk_list//implementation
+{
+	typedef std::unique_ptr<audio_chunk_impl> chunk_ptr_t;
+	std::vector<chunk_ptr_t> m_data, m_recycled;
+public:
+	dsp_chunk_list_impl() {}
+	dsp_chunk_list_impl(const dsp_chunk_list_impl&) = delete;
+	void operator=(const dsp_chunk_list_impl&) = delete;
+	t_size get_count() const;
+	audio_chunk * get_item(t_size n) const;
+	void remove_by_idx(t_size idx);
+	void remove_mask(const bit_array & mask);
+	audio_chunk * insert_item(t_size idx,t_size hint_size=0);
+
+	audio_chunk_impl* get_item_(size_t n) const { return m_data[n].get(); }
+};
+
+//! Instance of a DSP.\n
+//! Implementation: Derive from dsp_impl_base instead of deriving from dsp directly.\n
+//! Instantiation: Use dsp_entry static helper methods to instantiate DSPs, or dsp_chain_config / dsp_manager to deal with entire DSP chains.
+class NOVTABLE dsp : public service_base {
+public:
+	enum {
+		//! Flush whatever you need to when tracks change.
+		END_OF_TRACK = 1,
+		//! Flush everything.
+		FLUSH = 2	
+	};
+
+	//! @param p_chunk_list List of chunks to process. The implementation may alter the list in any way, inserting chunks of different sample rate / channel configuration etc.
+	//! @param p_cur_file Optional, location of currently decoded file. May be null.
+	//! @param p_flags Flags. Can be null, or a combination of END_OF_TRACK and FLUSH constants.
+	virtual void run(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags)=0;
+
+	//! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc.
+	virtual void flush() = 0;
+
+	//! Retrieves amount of data buffered by the DSP, for syncing visualisation.
+	//! @returns Amount of buffered audio data, in seconds.
+	virtual double get_latency() = 0;
+	//! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
+	//! Signaling this will force-flush any DSPs placed before this DSP so when it gets END_OF_TRACK, relevant chunks contain last samples of the track.\n
+	//! Signaling this will often break regular gapless playback so don't use it unless you have reasons to.
+	virtual bool need_track_change_mark() = 0;
+
+	void run_abortable(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags,abort_callback & p_abort);
+
+	//! Attempts to apply preset without recreating the DSP, if supported. 
+	//! @returns True on success, false if not supported (DSP needs re-creating).
+	bool apply_preset_(const dsp_preset&);
+
+	FB2K_MAKE_SERVICE_INTERFACE(dsp,service_base);
+};
+
+//! Backwards-compatible extension to dsp interface, allows abortable operation. Introduced in 0.9.2.
+class NOVTABLE dsp_v2 : public dsp {
+public:
+	//! Abortable version of dsp::run(). See dsp::run() for descriptions of parameters.
+	virtual void run_v2(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags,abort_callback & p_abort) = 0;
+private:
+	void run(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags) {
+		run_v2(p_chunk_list,p_cur_file,p_flags,fb2k::noAbort);
+	}
+
+	FB2K_MAKE_SERVICE_INTERFACE(dsp_v2,dsp);
+};
+
+//! Extended version allowing live changes in configuration without reinitialization.
+class NOVTABLE dsp_v3 : public dsp_v2 {
+	FB2K_MAKE_SERVICE_INTERFACE(dsp_v3, dsp_v2);
+public:
+	//! Live change of DSP settings. Return true if accepted, false if not (DSP will be destroyed and recreated).
+	virtual bool apply_preset(const dsp_preset&) = 0;
+};
+
+//! Helper class for implementing dsps. You should derive from dsp_impl_base instead of from dsp directly.\n
+//! The dsp_impl_base_t template allows you to use a custom interface class as a base class for your implementation, in case you provide extended functionality.\n
+//! Use dsp_factory_t<> template to register your dsp implementation.
+//! The implementation - as required by dsp_factory_t<> template - must also provide following methods:\n
+//! A constructor taking const dsp_preset&, initializing the DSP with specified preset data.\n
+//! static void g_get_name(pfc::string_base &); - retrieving human-readable name of the DSP to display.\n
+//! static bool g_get_default_preset(dsp_preset &); - retrieving default preset for this DSP. Return value is reserved for future use and should always be true.\n
+//! static GUID g_get_guid(); - retrieving GUID of your DSP implementation, to be used to identify it when storing DSP chain configuration.\n
+//! static bool g_have_config_popup(); - retrieving whether your DSP implementation supplies a popup dialog for configuring it.\n
+//! static void g_show_config_popup(const dsp_preset & p_data,HWND p_parent, dsp_preset_edit_callback & p_callback); - displaying your DSP's settings dialog; called only when g_have_config_popup() returns true; call p_callback.on_preset_changed() whenever user has made adjustments to the preset data.\n
+template<class t_baseclass>
+class dsp_impl_base_t : public t_baseclass {
+private:
+	typedef dsp_impl_base_t<t_baseclass> t_self;
+	dsp_chunk_list * m_list = nullptr;
+	t_size m_chunk_ptr = 0;
+	dsp_track_t m_cur_file = nullptr;
+	void run_v2(dsp_chunk_list * p_list,const dsp_track_t & p_cur_file,int p_flags,abort_callback & p_abort) override;
+protected:
+	//! Call only from on_chunk / on_endoftrack (on_endoftrack will give info on track being finished).\n
+	//! May return false when there's no known track and the metadb_handle ptr will be empty/null.
+	bool get_cur_file(dsp_track_t & p_out) const {p_out = m_cur_file; return p_out.is_valid();}
+    dsp_track_t get_cur_file() const { return m_cur_file; }
+	
+	dsp_impl_base_t() {}
+	
+	//! Inserts a new chunk of audio data. \n
+	//! You can call this only from on_chunk(), on_endofplayback() and on_endoftrack(). You're NOT allowed to call this from flush() which should just drop any queued data.
+	//! @param p_hint_size Optional, amount of buffer space that you require (in audio_samples). This is just a hint for memory allocation logic and will not cause the framework to allocate the chunk for you.
+	//! @returns A pointer to the newly allocated chunk. Pass the audio data you want to insert to this chunk object. The chunk is owned by the framework, you can't delete it etc.
+	audio_chunk * insert_chunk(t_size p_hint_size = 0) {
+		PFC_ASSERT(m_list != NULL);
+		return m_list->insert_item(m_chunk_ptr++,p_hint_size);
+	}
+	audio_chunk * insert_chunk( const audio_chunk & sourceCopy ) {
+		audio_chunk * c = insert_chunk( sourceCopy.get_used_size() );
+		c->copy( sourceCopy );
+		return c;
+	}
+
+
+	//! To be overridden by a DSP implementation.\n
+	//! Called on track change. You can use insert_chunk() to dump any data you have to flush. \n
+	//! Note that you must implement need_track_change_mark() to return true if you need this method called.
+	virtual void on_endoftrack(abort_callback & p_abort) = 0;
+	//! To be overridden by a DSP implementation.\n
+	//! Called at the end of played stream, typically at the end of last played track, to allow the DSP to return all data it has buffered-ahead.\n
+	//! Use insert_chunk() to return any data you have buffered.\n
+	//! Note that this call does not imply that the DSP will be destroyed next. \n
+	//! This is also called on track changes if some DSP placed after your DSP requests track change marks.
+	virtual void on_endofplayback(abort_callback & p_abort) = 0;
+	//! To be overridden by a DSP implementation.\n
+	//! Processes a chunk of audio data.\n
+	//! You can call insert_chunk() from inside on_chunk() to insert any audio data before currently processed chunk.\n
+	//! @param p_chunk Current chunk being processed. You can alter it in any way you like.
+	//! @returns True to keep p_chunk (with alterations made inside on_chunk()) in the stream, false to remove it.
+	virtual bool on_chunk(audio_chunk * p_chunk,abort_callback & p_abort) = 0;
+
+public:
+	//! To be overridden by a DSP implementation.\n
+	//! Flushes the DSP (drops any buffered data). The implementation should reset the DSP to the same state it was in before receiving any audio data. \n
+	//! Called after seeking, etc.
+	virtual void flush() override = 0;
+	//! To be overridden by a DSP implementation.\n
+	//! Retrieves amount of data buffered by the DSP, for syncing visualisation.
+	//! @returns Amount of buffered audio data, in seconds.
+	virtual double get_latency() override = 0;
+	//! To be overridden by a DSP implementation.\n
+	//! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
+	//! Signaling this will force-flush any DSPs placed before this DSP so when it gets on_endoftrack(), relevant chunks contain last samples of the track.\n
+	//! Signaling this may interfere with gapless playback in certain scenarios (forces flush of DSPs placed before you) so don't use it unless you have reasons to.
+	virtual bool need_track_change_mark() override = 0;
+private:
+	dsp_impl_base_t(const t_self&) = delete;
+	const t_self & operator=(const t_self &) = delete;
+};
+
+template<class t_baseclass>
+void dsp_impl_base_t<t_baseclass>::run_v2(dsp_chunk_list * p_list,const dsp_track_t & p_cur_file,int p_flags,abort_callback & p_abort) {
+	pfc::vartoggle_t<dsp_chunk_list*> l_list_toggle(m_list,p_list);
+    auto track_toggle = pfc::autoToggle(m_cur_file, p_cur_file);
+	
+	for(m_chunk_ptr = 0;m_chunk_ptr<m_list->get_count();m_chunk_ptr++) {
+		audio_chunk * c = m_list->get_item(m_chunk_ptr);
+		if (c->is_empty() || !on_chunk(c,p_abort))
+			m_list->remove_by_idx(m_chunk_ptr--);
+	}
+
+	if (p_flags & dsp::FLUSH) {
+		on_endofplayback(p_abort);
+	} else if (p_flags & dsp::END_OF_TRACK) {
+		if (need_track_change_mark()) on_endoftrack(p_abort);
+	}
+}
+
+
+typedef dsp_impl_base_t<dsp_v2> dsp_impl_base;
+
+class NOVTABLE dsp_preset {
+public:
+	virtual GUID get_owner() const = 0;
+	virtual void set_owner(const GUID & p_owner) = 0;
+	virtual const void * get_data() const = 0;
+	virtual t_size get_data_size() const = 0;
+	virtual void set_data(const void * p_data,t_size p_data_size) = 0;
+	virtual void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) = 0;
+
+	const dsp_preset & operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
+
+	void copy(const dsp_preset & p_source) {set_owner(p_source.get_owner());set_data(p_source.get_data(),p_source.get_data_size());}
+
+	void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
+	void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
+	static void g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort);
+
+	bool operator==(const dsp_preset & p_other) const {
+		if (get_owner() != p_other.get_owner()) return false;
+		if (get_data_size() != p_other.get_data_size()) return false;
+		if (memcmp(get_data(),p_other.get_data(),get_data_size()) != 0) return false;
+		return true;
+	}
+	bool operator!=(const dsp_preset & p_other) const {
+		return !(*this == p_other);
+	}
+
+	pfc::string8 get_owner_name() const;
+	pfc::string8 get_owner_name_debug() const;
+	pfc::string8 debug(const char * knownName = nullptr) const;
+protected:
+	dsp_preset() {}
+	~dsp_preset() {}
+};
+
+class dsp_preset_writer : public stream_writer {
+public:
+	void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
+		p_abort.check();
+		m_data.append_fromptr((const t_uint8 *) p_buffer,p_bytes);
+	}
+	void flush(dsp_preset & p_preset) {
+		p_preset.set_data(m_data.get_ptr(),m_data.get_size());
+		m_data.set_size(0);
+	}
+private:
+	pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> m_data;
+};
+
+class dsp_preset_reader : public stream_reader {
+public:
+	dsp_preset_reader() : m_walk(0) {}
+	dsp_preset_reader(const dsp_preset_reader & p_source) : m_walk(0) {*this = p_source;}
+	void init(const dsp_preset & p_preset) {
+		m_data.set_data_fromptr( (const t_uint8*) p_preset.get_data(), p_preset.get_data_size() );
+		m_walk = 0;
+	}
+	t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
+		p_abort.check();
+		t_size todo = pfc::min_t<t_size>(p_bytes,m_data.get_size()-m_walk);
+		memcpy(p_buffer,m_data.get_ptr()+m_walk,todo);
+		m_walk += todo;
+		return todo;
+	}
+	bool is_finished() {return m_walk == m_data.get_size();}
+private:
+	t_size m_walk;
+	pfc::array_t<t_uint8> m_data;
+};
+
+class dsp_preset_impl : public dsp_preset
+{
+public:
+	dsp_preset_impl() {}
+	dsp_preset_impl(const dsp_preset_impl & p_source) {copy(p_source);}
+	dsp_preset_impl(const dsp_preset & p_source) {copy(p_source);}
+	dsp_preset_impl(dsp_preset_impl && p_source) noexcept {move(p_source);}
+	void clear() {m_owner = pfc::guid_null; m_data.clear();}
+	bool is_valid() const { return m_owner != pfc::guid_null; }
+
+	const dsp_preset_impl& operator=(const dsp_preset_impl & p_source) {copy(p_source); return *this;}
+	const dsp_preset_impl& operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
+	const dsp_preset_impl& operator=(dsp_preset_impl&& p_source) noexcept { move(p_source); return *this; }
+
+	GUID get_owner() const {return m_owner;}
+	void set_owner(const GUID & p_owner) {m_owner = p_owner;}
+	const void * get_data() const {return m_data.ptr();}
+	t_size get_data_size() const {return m_data.size();}
+	void set_data(const void * p_data,t_size p_data_size) {m_data.set_data_fromptr((const t_uint8*)p_data,p_data_size);}
+	void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort);
+
+	void move(dsp_preset_impl& source) noexcept {
+		m_owner = source.m_owner;
+		m_data = std::move(source.m_data);
+	}
+
+	void set_data(pfc::mem_block&& data) { m_data = std::move(data); }
+private:
+	GUID m_owner = {};
+	pfc::mem_block m_data;
+};
+
+class NOVTABLE dsp_preset_edit_callback {
+public:
+	virtual void on_preset_changed(const dsp_preset &) = 0;
+private:
+	dsp_preset_edit_callback(const dsp_preset_edit_callback&) = delete;
+	const dsp_preset_edit_callback & operator=(const dsp_preset_edit_callback &) = delete;
+protected:
+	dsp_preset_edit_callback() {}
+	~dsp_preset_edit_callback() {}
+};
+
+class NOVTABLE dsp_preset_edit_callback_v2 : public service_base {
+    FB2K_MAKE_SERVICE_INTERFACE(dsp_preset_edit_callback_v2, service_base);
+public:
+    virtual void get_preset( dsp_preset & outPreset ) = 0;
+    virtual void set_preset( const dsp_preset & inPreset ) = 0;
+    virtual void dsp_dialog_done( bool bOK ) = 0;
+    void reset();
+};
+
+
+class NOVTABLE dsp_entry : public service_base {
+public:
+	virtual void get_name(pfc::string_base & p_out) = 0;
+	virtual bool get_default_preset(dsp_preset & p_out) = 0;
+	virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;	
+	virtual GUID get_guid() = 0;
+	virtual bool have_config_popup() = 0;
+    
+#ifdef FOOBAR2000_MOBILE
+    virtual void show_config_popup( fb2k::dspConfigContext_t parent, dsp_preset_edit_callback_v2::ptr callback ) {}
+#endif
+
+#ifdef FOOBAR2000_DESKTOP
+#ifdef _WIN32
+	//! Shows configuration popup. Call from main thread only! \n
+	//! Blocks until done. Returns true if preset has been altered, false otherwise.
+	//! Legacy method, replaced in dsp_entry_v2 and newer.
+	virtual bool show_config_popup(dsp_preset& p_data, fb2k::hwnd_t p_parent) { (void)p_data; (void)p_parent; return false; }
+#else // non-Windows desktop
+	//! Shows configuration popup. Main thread only! \n
+    //! Mac: returns NSObjectWrapper holding NSViewController
+	virtual service_ptr show_config_popup(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback) { (void)parent; (void)callback; throw pfc::exception_not_implemented(); }
+#endif
+#endif // FOOBAR2000_DESKTOP
+
+	//! Obsolete method, hidden DSPs now use a different entry class.
+	bool is_user_accessible() { return true; }
+
+    static constexpr unsigned flag_playback = 1 << 0,
+        flag_conversion = 1 << 1;
+    
+	static bool g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid);
+	static service_ptr_t<dsp_entry> g_get_interface(const GUID&);
+	static bool g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset, unsigned flags = 0);
+	static bool g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid);
+	static bool g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid);
+	static bool g_dsp_exists(const GUID & p_guid);
+	static bool g_get_default_preset(dsp_preset & p_out,const GUID & p_guid);
+	static bool g_have_config_popup(const GUID & p_guid);
+	static bool g_have_config_popup(const dsp_preset & p_preset);
+#ifdef _WIN32
+	//! Shows configuration popup. Main thread only! \n
+	//! Blocks until done. Returns true if preset has been altered, false otherwise.
+	static bool g_show_config_popup(dsp_preset & p_preset,fb2k::hwnd_t p_parent);
+	//! Shows configuration popup. Main thread only!
+	//! Blocks until done. Uses callback to notify host about preset change.
+	static void g_show_config_popup_v2(const dsp_preset & p_preset,fb2k::hwnd_t p_parent,dsp_preset_edit_callback & p_callback);
+
+	//! Shows configuration popup. Main thread only! \n
+	//! Blocks until done. Uses callback to notify host about preset change. \n
+	//! Implements a fallback using legacy methods if show_config_popup_v2() is not available. \n
+	//! @returns OK/cancel status (true/false), if the dialog supports it; otherwise always true.
+	bool show_config_popup_v2_(const dsp_preset& p_preset, fb2k::hwnd_t p_parent, dsp_preset_edit_callback& p_callback);
+
+	//! Shows configuration popup. Main thread only! \n
+	//! May either block until done and return null, or run asynchronously and return an object to release to cancel the dialog. \n
+	//! Implements a fallback using legacy methods if show_config_popup_v3() is not available.
+	service_ptr show_config_popup_v3_(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback);
+#endif
+
+#ifdef FOOBAR2000_MOBILE
+	static void g_show_config_popup( fb2k::dspConfigContext_t parent, dsp_preset_edit_callback_v2::ptr callback);
+#endif
+
+	bool get_display_name_supported();
+	void get_display_name_(const dsp_preset& arg, pfc::string_base& out);
+	bool enumerate_default_presets_(dsp_chain_config& ret);
+	bool match_preset_subclass_(dsp_preset const& x, dsp_preset const& y);
+
+	FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry);
+};
+
+class NOVTABLE dsp_entry_v2 : public dsp_entry {
+public:
+#ifdef _WIN32
+	//! Shows configuration popup. Main thread only!
+	virtual void show_config_popup_v2(const dsp_preset & p_data,fb2k::hwnd_t p_parent,dsp_preset_edit_callback & p_callback) = 0;
+	// Obsolete method, redirected to show_config_popup_v2() by default, no need to implement.
+	bool show_config_popup(dsp_preset& p_data, fb2k::hwnd_t p_parent) override;
+#endif
+private:
+    
+	FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v2,dsp_entry);
+};
+
+//! \since Late 1.6.x
+class NOVTABLE dsp_entry_v3 : public dsp_entry_v2 {
+	FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v3, dsp_entry_v2);
+public:
+	//! Returns the text to show in DSP list, for this specific preset.
+	virtual void get_display_name(const dsp_preset& arg, pfc::string_base& out) = 0;
+
+#ifdef _WIN32
+	//! Shows configuration popup, asynchronous version - creates dialog then returns immediately. \n
+	//! Since not every DSP implements this, caller must be prepared to call legacy blocking show_config_popup methods instead. \n
+	//! show_config_popup_v3() may throw pfc::exception_not_implemented() to signal host that this DSP doesn't support this method yet. \n
+	//! Main thread only! \n
+	//! @returns Object to retain by host, to be released to request the dialog to be closed.
+	virtual service_ptr show_config_popup_v3(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback) = 0;
+#endif
+};
+
+//! \since 2.1
+class NOVTABLE dsp_entry_v4 : public dsp_entry_v3 {
+    FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v4, dsp_entry_v3);
+public:
+    virtual dsp::ptr instantiate_v4( const dsp_preset & arg, unsigned flags ) = 0;
+};
+
+//! \since 2.2
+class NOVTABLE dsp_entry_v5 : public dsp_entry_v4 {
+	FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v5, dsp_entry_v4);
+public:
+	//! If your DSP implementation is meant to preset as multiple item in available DSP list, implement this method. \n
+	//! @returns True if preset list has been returned (your DSP will be hidden if blank), false if your DSP is meant to be shown as just one item.
+	virtual bool enumerate_default_presets(dsp_chain_config& ret) { (void)ret; return false; }
+	//! Can possibly reach state Y by editing state X, and vice versa? \n
+	//! If this DSP has no configuration UI, this should just test if the presets are identical.
+	//! Frontend will use this to pin running presets to one of available DSP list items.
+	virtual bool match_preset_subclass(dsp_preset const& x, dsp_preset const& y) { (void)x; (void)y; return true; }
+};
+
+class NOVTABLE dsp_entry_hidden : public service_base {
+	FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry_hidden);
+public:
+	//! Obsolete method, hidden DSPs now use a different entry class from ordinary ones.
+	bool is_user_accessible() {return false; }
+
+	static bool g_get_interface( dsp_entry_hidden::ptr & out, const GUID & guid );
+	static bool g_instantiate( dsp::ptr & out, const dsp_preset & preset );
+	static bool g_dsp_exists(const GUID & p_guid);
+
+	virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;	
+	virtual GUID get_guid() = 0;
+};
+
+template<class T,class t_entry = dsp_entry>
+class dsp_entry_impl_nopreset_t : public t_entry {
+public:
+	void get_name(pfc::string_base & p_out) override  {T::g_get_name(p_out);}
+	bool get_default_preset(dsp_preset & p_out) override
+	{
+		p_out.set_owner(T::g_get_guid());
+		p_out.set_data(0,0);
+		return true;
+	}
+	bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) override
+	{
+		if (p_preset.get_owner() == T::g_get_guid() && p_preset.get_data_size() == 0)
+		{
+			p_out = new service_impl_t<T>();
+			return p_out.is_valid();
+		}
+		else return false;
+	}
+	GUID get_guid() override  {return T::g_get_guid();}
+
+	bool have_config_popup() override {return false;}
+};
+
+template<typename T, typename interface_t>
+class dsp_entry_common_t : public interface_t {
+public:
+	void get_name(pfc::string_base& p_out) override { T::g_get_name(p_out); }
+	bool get_default_preset(dsp_preset& p_out) override { return T::g_get_default_preset(p_out); }
+	bool instantiate(service_ptr_t<dsp>& p_out, const dsp_preset& p_preset) override {
+		if (p_preset.get_owner() == T::g_get_guid()) {
+			p_out = new service_impl_t<T>(p_preset);
+			return true;
+		} else return false;
+	}
+	GUID get_guid() override { return T::g_get_guid(); }
+
+	bool have_config_popup() override { return T::g_have_config_popup(); }
+#ifdef FOOBAR2000_MOBILE
+    void show_config_popup( fb2k::dspConfigContext_t parent, dsp_preset_edit_callback_v2::ptr callback ) override { T::g_show_config_popup(parent, callback); }
+#endif
+#if defined(FOOBAR2000_DESKTOP) && !defined(_WIN32)
+    service_ptr show_config_popup( fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback ) override {
+        return T::g_show_config_popup(parent, callback);
+    }
+#endif
+};
+
+template<class T, class t_entry = dsp_entry>
+class dsp_entry_impl_t : public dsp_entry_common_t<T, t_entry> {
+public:
+#ifdef _WIN32
+    bool show_config_popup(dsp_preset & p_data,fb2k::hwnd_t p_parent) override {return T::g_show_config_popup(p_data,p_parent);}
+#endif
+};
+
+template<class T, class t_entry = dsp_entry_v2>
+class dsp_entry_v2_impl_t : public dsp_entry_common_t<T, t_entry> {
+public:
+#ifdef _WIN32
+	void show_config_popup_v2(const dsp_preset & p_data,fb2k::hwnd_t p_parent,dsp_preset_edit_callback & p_callback) override {T::g_show_config_popup(p_data,p_parent,p_callback);}
+#endif
+};
+
+template<class T, class t_entry = dsp_entry_v3>
+class dsp_entry_v3_impl_t : public dsp_entry_v2_impl_t<T, t_entry> {
+public:
+	void get_display_name(const dsp_preset& arg, pfc::string_base& out) override {
+		T::g_get_display_name(arg, out);
+	}
+
+#ifdef _WIN32
+	service_ptr show_config_popup_v3(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback) override {
+		return T::g_show_config_popup_v3(parent, callback);
+	}
+#endif
+};
+
+template<class dsp_t, class entry_t = dsp_entry_v4>
+class dsp_entry_v4_impl_t : public dsp_entry_v3_impl_t< dsp_t, entry_t > {
+public:
+    dsp::ptr instantiate_v4( const dsp_preset & arg, unsigned flags ) override {
+        PFC_ASSERT( arg.get_owner() == dsp_t::g_get_guid() );
+        return new service_impl_t< dsp_t > ( arg, flags );
+    }
+};
+
+template<class dsp_t, class entry_t = dsp_entry_v5>
+class dsp_entry_v5_impl_t : public dsp_entry_v4_impl_t< dsp_t, entry_t > {
+public:
+	bool enumerate_default_presets(dsp_chain_config& ret) { 
+		return dsp_t::g_enumerate_default_presets(ret);
+	}
+	bool match_preset_subclass(dsp_preset const& x, dsp_preset const& y) {
+		return dsp_t::g_match_preset_subclass(x, y);
+	}
+};
+
+template<typename dsp_t>
+class dsp_entry_hidden_t : public dsp_entry_hidden {
+public:
+	bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) override {
+		if (p_preset.get_owner() == dsp_t::g_get_guid()) {
+			p_out = new service_impl_t<dsp_t>(p_preset);
+			return true;
+		} else return false;
+	}
+	GUID get_guid() override {return dsp_t::g_get_guid();}
+};
+
+template<typename dsp_t, typename interface_t>
+class implement_dsp_entry;
+
+template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry> : public dsp_entry_impl_t<dsp_t> {};
+template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v2> : public dsp_entry_v2_impl_t<dsp_t> {};
+template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v3> : public dsp_entry_v3_impl_t<dsp_t> {};
+template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v4> : public dsp_entry_v4_impl_t<dsp_t> {};
+template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v5> : public dsp_entry_v5_impl_t<dsp_t> {};
+template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_hidden> : public dsp_entry_hidden_t<dsp_t> {};
+
+template<class T>
+class dsp_factory_nopreset_t : public service_factory_single_t<dsp_entry_impl_nopreset_t<T> > {};
+
+template<typename dsp_t, typename interface_t = dsp_entry_v2>
+class dsp_factory_t : public service_factory_single_t<implement_dsp_entry<dsp_t, interface_t> > {};
+
+template<class T>
+class dsp_factory_hidden_t : public service_factory_single_t< dsp_entry_hidden_t<T> > {};
+
+class NOVTABLE dsp_chain_config
+{
+public:
+	virtual t_size get_count() const = 0;
+	virtual const dsp_preset & get_item(t_size p_index) const = 0;
+	virtual void replace_item(const dsp_preset & p_data,t_size p_index) = 0;
+	virtual void insert_item(const dsp_preset & p_data,t_size p_index) = 0;
+	virtual void remove_mask(const bit_array & p_mask) = 0;
+	
+	void remove_item(t_size p_index);
+	void remove_all();
+	void add_item(const dsp_preset & p_data);
+	void copy(const dsp_chain_config & p_source);
+    void add_items(const dsp_chain_config & p_source);
+
+	const dsp_chain_config & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
+
+	void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
+	void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
+	fb2k::memBlock::ptr to_blob() const;
+	void from_blob(const void* p, size_t size);
+	void from_blob(fb2k::memBlock::ptr);
+   
+	pfc::string8 get_name_list() const;
+	void get_name_list(pfc::string_base & p_out) const;
+
+	static bool equals(dsp_chain_config const & v1, dsp_chain_config const & v2);
+	static bool equals_debug(dsp_chain_config const& v1, dsp_chain_config const& v2);
+
+    //! Helpers to enable/disable specific DSP in this chain. Return true if the chain has been altered, false otherwise.
+    bool enable_dsp( const GUID & dspID );
+    //! Helpers to enable/disable specific DSP in this chain. Return true if the chain has been altered, false otherwise.
+    bool disable_dsp( const GUID & dspID );
+    //! Helpers to enable/disable specific DSP in this chain. Return true if the chain has been altered, false otherwise.
+    bool enable_dsp( const dsp_preset & preset );
+
+    size_t find_first_of_type( const GUID & dspID ) const;
+    bool contains_dsp( const GUID & dspID ) const;
+	
+	pfc::string8 debug() const;
+
+	bool operator==(const dsp_chain_config & other) const {return equals(*this, other);}
+	bool operator!=(const dsp_chain_config & other) const {return !equals(*this, other);}
+};
+
+FB2K_STREAM_READER_OVERLOAD(dsp_chain_config) {
+	value.contents_from_stream(&stream.m_stream, stream.m_abort); return stream;
+}
+
+FB2K_STREAM_WRITER_OVERLOAD(dsp_chain_config) {
+	value.contents_to_stream(&stream.m_stream, stream.m_abort); return stream;
+}
+
+class dsp_chain_config_impl : public dsp_chain_config
+{
+public:
+	dsp_chain_config_impl() {}
+	dsp_chain_config_impl(const dsp_chain_config & p_source) {copy(p_source);}
+	dsp_chain_config_impl(const dsp_chain_config_impl & p_source) { copy_v2(p_source);}
+	dsp_chain_config_impl(dsp_chain_config_impl&& p_source) noexcept : m_data(std::move(p_source.m_data)) {}
+	t_size get_count() const override;
+	const dsp_preset & get_item(t_size p_index) const override;
+	void replace_item(const dsp_preset & p_data,t_size p_index) override;
+	void insert_item(const dsp_preset & p_data,t_size p_index) override;
+	void remove_mask(const bit_array & p_mask) override;
+	
+	const char* get_dsp_name(size_t idx) const;
+	void insert_item_v2(const dsp_preset& data, const char* dspName, size_t index);
+	void add_item_v2(const dsp_preset& data, const char* dspName);
+	void copy_v2(dsp_chain_config_impl const&);
+	pfc::string8 debug() const;
+
+	const dsp_chain_config_impl & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
+	const dsp_chain_config_impl & operator=(const dsp_chain_config_impl & p_source) {copy_v2(p_source); return *this;}
+	const dsp_chain_config_impl & operator=(dsp_chain_config_impl&& p_source) noexcept { m_data = std::move(p_source.m_data); p_source.m_data.remove_all(); return *this; }
+
+	~dsp_chain_config_impl();
+
+	void reorder( const size_t * order, size_t count );
+
+	void supply_name(size_t idx, pfc::string8 && name) { m_data[idx]->dspName = std::move(name); }
+	const char* find_dsp_name(const GUID& guid) const;
+private:
+
+	struct entry_t {
+		dsp_preset_impl data;
+		pfc::string8 dspName;
+	};
+
+
+	pfc::ptr_list_t<entry_t> m_data;
+};
+
+//! Helper.
+class dsp_preset_parser : public stream_reader_formatter<> {
+public:
+	dsp_preset_parser(const dsp_preset& in) : stream_reader_formatter(_m_stream, fb2k::noAbort), m_data(in), _m_stream(in.get_data(), in.get_data_size()) {}
+
+	void reset() { _m_stream.reset(); }
+	t_size get_remaining() const { return _m_stream.get_remaining(); }
+
+	void assume_empty() const {
+		if (get_remaining() != 0) throw exception_io_data();
+	}
+
+	GUID get_owner() const { return m_data.get_owner(); }
+private:
+	const dsp_preset& m_data;
+	stream_reader_memblock_ref _m_stream;
+};
+
+//! Helper.
+class dsp_preset_builder : public stream_writer_formatter<> {
+public:
+	dsp_preset_builder() : stream_writer_formatter(_m_stream, fb2k::noAbort) {}
+	void finish(const GUID& id, dsp_preset& out) {
+		out.set_owner(id);
+		out.set_data(_m_stream.m_buffer.get_ptr(), _m_stream.m_buffer.get_size());
+	}
+	void reset() {
+		_m_stream.m_buffer.set_size(0);
+	}
+private:
+	stream_writer_buffer_simple _m_stream;
+};
+
+#ifdef __APPLE__
+// NSObjectWrapper
+#include "commonObjects-Apple.h"
+#endif
+
+#endif