|
1
|
1 #pragma once
|
|
|
2 #include "audio_chunk.h"
|
|
|
3 #include "filesystem.h"
|
|
|
4
|
|
|
5 #ifdef FOOBAR2000_HAVE_DSP
|
|
|
6
|
|
|
7 #ifdef FOOBAR2000_MOBILE
|
|
|
8 #include "dsp-context.h"
|
|
|
9 #endif
|
|
|
10
|
|
|
11 #include <memory>
|
|
|
12 #include <vector>
|
|
|
13
|
|
|
14 class dsp_preset; class dsp_chain_config; // forward declaration
|
|
|
15
|
|
|
16 #ifdef FOOBAR2000_HAVE_METADB
|
|
|
17 typedef metadb_handle_ptr dsp_track_t;
|
|
|
18 #else
|
|
|
19 typedef trackRef dsp_track_t;
|
|
|
20 #endif
|
|
|
21
|
|
|
22 //! 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.
|
|
|
23 class NOVTABLE dsp_chunk_list {
|
|
|
24 public:
|
|
|
25 virtual t_size get_count() const = 0;
|
|
|
26 virtual audio_chunk * get_item(t_size n) const = 0;
|
|
|
27 virtual void remove_by_idx(t_size idx) = 0;
|
|
|
28 virtual void remove_mask(const bit_array & mask) = 0;
|
|
|
29 virtual audio_chunk * insert_item(t_size idx,t_size hint_size=0) = 0;
|
|
|
30
|
|
|
31 audio_chunk * add_item(t_size hint_size=0);
|
|
|
32
|
|
|
33 void remove_all();
|
|
|
34
|
|
|
35 double get_duration();
|
|
|
36
|
|
|
37 void add_chunk(const audio_chunk * chunk);
|
|
|
38
|
|
|
39 void remove_bad_chunks();
|
|
|
40 protected:
|
|
|
41 dsp_chunk_list() {}
|
|
|
42 ~dsp_chunk_list() {}
|
|
|
43 };
|
|
|
44
|
|
|
45 class dsp_chunk_list_impl : public dsp_chunk_list//implementation
|
|
|
46 {
|
|
|
47 typedef std::unique_ptr<audio_chunk_impl> chunk_ptr_t;
|
|
|
48 std::vector<chunk_ptr_t> m_data, m_recycled;
|
|
|
49 public:
|
|
|
50 dsp_chunk_list_impl() {}
|
|
|
51 dsp_chunk_list_impl(const dsp_chunk_list_impl&) = delete;
|
|
|
52 void operator=(const dsp_chunk_list_impl&) = delete;
|
|
|
53 t_size get_count() const;
|
|
|
54 audio_chunk * get_item(t_size n) const;
|
|
|
55 void remove_by_idx(t_size idx);
|
|
|
56 void remove_mask(const bit_array & mask);
|
|
|
57 audio_chunk * insert_item(t_size idx,t_size hint_size=0);
|
|
|
58
|
|
|
59 audio_chunk_impl* get_item_(size_t n) const { return m_data[n].get(); }
|
|
|
60 };
|
|
|
61
|
|
|
62 //! Instance of a DSP.\n
|
|
|
63 //! Implementation: Derive from dsp_impl_base instead of deriving from dsp directly.\n
|
|
|
64 //! Instantiation: Use dsp_entry static helper methods to instantiate DSPs, or dsp_chain_config / dsp_manager to deal with entire DSP chains.
|
|
|
65 class NOVTABLE dsp : public service_base {
|
|
|
66 public:
|
|
|
67 enum {
|
|
|
68 //! Flush whatever you need to when tracks change.
|
|
|
69 END_OF_TRACK = 1,
|
|
|
70 //! Flush everything.
|
|
|
71 FLUSH = 2
|
|
|
72 };
|
|
|
73
|
|
|
74 //! @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.
|
|
|
75 //! @param p_cur_file Optional, location of currently decoded file. May be null.
|
|
|
76 //! @param p_flags Flags. Can be null, or a combination of END_OF_TRACK and FLUSH constants.
|
|
|
77 virtual void run(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags)=0;
|
|
|
78
|
|
|
79 //! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc.
|
|
|
80 virtual void flush() = 0;
|
|
|
81
|
|
|
82 //! Retrieves amount of data buffered by the DSP, for syncing visualisation.
|
|
|
83 //! @returns Amount of buffered audio data, in seconds.
|
|
|
84 virtual double get_latency() = 0;
|
|
|
85 //! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
|
|
|
86 //! 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
|
|
|
87 //! Signaling this will often break regular gapless playback so don't use it unless you have reasons to.
|
|
|
88 virtual bool need_track_change_mark() = 0;
|
|
|
89
|
|
|
90 void run_abortable(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags,abort_callback & p_abort);
|
|
|
91
|
|
|
92 //! Attempts to apply preset without recreating the DSP, if supported.
|
|
|
93 //! @returns True on success, false if not supported (DSP needs re-creating).
|
|
|
94 bool apply_preset_(const dsp_preset&);
|
|
|
95
|
|
|
96 FB2K_MAKE_SERVICE_INTERFACE(dsp,service_base);
|
|
|
97 };
|
|
|
98
|
|
|
99 //! Backwards-compatible extension to dsp interface, allows abortable operation. Introduced in 0.9.2.
|
|
|
100 class NOVTABLE dsp_v2 : public dsp {
|
|
|
101 public:
|
|
|
102 //! Abortable version of dsp::run(). See dsp::run() for descriptions of parameters.
|
|
|
103 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;
|
|
|
104 private:
|
|
|
105 void run(dsp_chunk_list * p_chunk_list,const dsp_track_t & p_cur_file,int p_flags) {
|
|
|
106 run_v2(p_chunk_list,p_cur_file,p_flags,fb2k::noAbort);
|
|
|
107 }
|
|
|
108
|
|
|
109 FB2K_MAKE_SERVICE_INTERFACE(dsp_v2,dsp);
|
|
|
110 };
|
|
|
111
|
|
|
112 //! Extended version allowing live changes in configuration without reinitialization.
|
|
|
113 class NOVTABLE dsp_v3 : public dsp_v2 {
|
|
|
114 FB2K_MAKE_SERVICE_INTERFACE(dsp_v3, dsp_v2);
|
|
|
115 public:
|
|
|
116 //! Live change of DSP settings. Return true if accepted, false if not (DSP will be destroyed and recreated).
|
|
|
117 virtual bool apply_preset(const dsp_preset&) = 0;
|
|
|
118 };
|
|
|
119
|
|
|
120 //! Helper class for implementing dsps. You should derive from dsp_impl_base instead of from dsp directly.\n
|
|
|
121 //! 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
|
|
|
122 //! Use dsp_factory_t<> template to register your dsp implementation.
|
|
|
123 //! The implementation - as required by dsp_factory_t<> template - must also provide following methods:\n
|
|
|
124 //! A constructor taking const dsp_preset&, initializing the DSP with specified preset data.\n
|
|
|
125 //! static void g_get_name(pfc::string_base &); - retrieving human-readable name of the DSP to display.\n
|
|
|
126 //! 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
|
|
|
127 //! static GUID g_get_guid(); - retrieving GUID of your DSP implementation, to be used to identify it when storing DSP chain configuration.\n
|
|
|
128 //! static bool g_have_config_popup(); - retrieving whether your DSP implementation supplies a popup dialog for configuring it.\n
|
|
|
129 //! 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
|
|
|
130 template<class t_baseclass>
|
|
|
131 class dsp_impl_base_t : public t_baseclass {
|
|
|
132 private:
|
|
|
133 typedef dsp_impl_base_t<t_baseclass> t_self;
|
|
|
134 dsp_chunk_list * m_list = nullptr;
|
|
|
135 t_size m_chunk_ptr = 0;
|
|
|
136 dsp_track_t m_cur_file = nullptr;
|
|
|
137 void run_v2(dsp_chunk_list * p_list,const dsp_track_t & p_cur_file,int p_flags,abort_callback & p_abort) override;
|
|
|
138 protected:
|
|
|
139 //! Call only from on_chunk / on_endoftrack (on_endoftrack will give info on track being finished).\n
|
|
|
140 //! May return false when there's no known track and the metadb_handle ptr will be empty/null.
|
|
|
141 bool get_cur_file(dsp_track_t & p_out) const {p_out = m_cur_file; return p_out.is_valid();}
|
|
|
142 dsp_track_t get_cur_file() const { return m_cur_file; }
|
|
|
143
|
|
|
144 dsp_impl_base_t() {}
|
|
|
145
|
|
|
146 //! Inserts a new chunk of audio data. \n
|
|
|
147 //! 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.
|
|
|
148 //! @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.
|
|
|
149 //! @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.
|
|
|
150 audio_chunk * insert_chunk(t_size p_hint_size = 0) {
|
|
|
151 PFC_ASSERT(m_list != NULL);
|
|
|
152 return m_list->insert_item(m_chunk_ptr++,p_hint_size);
|
|
|
153 }
|
|
|
154 audio_chunk * insert_chunk( const audio_chunk & sourceCopy ) {
|
|
|
155 audio_chunk * c = insert_chunk( sourceCopy.get_used_size() );
|
|
|
156 c->copy( sourceCopy );
|
|
|
157 return c;
|
|
|
158 }
|
|
|
159
|
|
|
160
|
|
|
161 //! To be overridden by a DSP implementation.\n
|
|
|
162 //! Called on track change. You can use insert_chunk() to dump any data you have to flush. \n
|
|
|
163 //! Note that you must implement need_track_change_mark() to return true if you need this method called.
|
|
|
164 virtual void on_endoftrack(abort_callback & p_abort) = 0;
|
|
|
165 //! To be overridden by a DSP implementation.\n
|
|
|
166 //! 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
|
|
|
167 //! Use insert_chunk() to return any data you have buffered.\n
|
|
|
168 //! Note that this call does not imply that the DSP will be destroyed next. \n
|
|
|
169 //! This is also called on track changes if some DSP placed after your DSP requests track change marks.
|
|
|
170 virtual void on_endofplayback(abort_callback & p_abort) = 0;
|
|
|
171 //! To be overridden by a DSP implementation.\n
|
|
|
172 //! Processes a chunk of audio data.\n
|
|
|
173 //! You can call insert_chunk() from inside on_chunk() to insert any audio data before currently processed chunk.\n
|
|
|
174 //! @param p_chunk Current chunk being processed. You can alter it in any way you like.
|
|
|
175 //! @returns True to keep p_chunk (with alterations made inside on_chunk()) in the stream, false to remove it.
|
|
|
176 virtual bool on_chunk(audio_chunk * p_chunk,abort_callback & p_abort) = 0;
|
|
|
177
|
|
|
178 public:
|
|
|
179 //! To be overridden by a DSP implementation.\n
|
|
|
180 //! 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
|
|
|
181 //! Called after seeking, etc.
|
|
|
182 virtual void flush() override = 0;
|
|
|
183 //! To be overridden by a DSP implementation.\n
|
|
|
184 //! Retrieves amount of data buffered by the DSP, for syncing visualisation.
|
|
|
185 //! @returns Amount of buffered audio data, in seconds.
|
|
|
186 virtual double get_latency() override = 0;
|
|
|
187 //! To be overridden by a DSP implementation.\n
|
|
|
188 //! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
|
|
|
189 //! 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
|
|
|
190 //! 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.
|
|
|
191 virtual bool need_track_change_mark() override = 0;
|
|
|
192 private:
|
|
|
193 dsp_impl_base_t(const t_self&) = delete;
|
|
|
194 const t_self & operator=(const t_self &) = delete;
|
|
|
195 };
|
|
|
196
|
|
|
197 template<class t_baseclass>
|
|
|
198 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) {
|
|
|
199 pfc::vartoggle_t<dsp_chunk_list*> l_list_toggle(m_list,p_list);
|
|
|
200 auto track_toggle = pfc::autoToggle(m_cur_file, p_cur_file);
|
|
|
201
|
|
|
202 for(m_chunk_ptr = 0;m_chunk_ptr<m_list->get_count();m_chunk_ptr++) {
|
|
|
203 audio_chunk * c = m_list->get_item(m_chunk_ptr);
|
|
|
204 if (c->is_empty() || !on_chunk(c,p_abort))
|
|
|
205 m_list->remove_by_idx(m_chunk_ptr--);
|
|
|
206 }
|
|
|
207
|
|
|
208 if (p_flags & dsp::FLUSH) {
|
|
|
209 on_endofplayback(p_abort);
|
|
|
210 } else if (p_flags & dsp::END_OF_TRACK) {
|
|
|
211 if (need_track_change_mark()) on_endoftrack(p_abort);
|
|
|
212 }
|
|
|
213 }
|
|
|
214
|
|
|
215
|
|
|
216 typedef dsp_impl_base_t<dsp_v2> dsp_impl_base;
|
|
|
217
|
|
|
218 class NOVTABLE dsp_preset {
|
|
|
219 public:
|
|
|
220 virtual GUID get_owner() const = 0;
|
|
|
221 virtual void set_owner(const GUID & p_owner) = 0;
|
|
|
222 virtual const void * get_data() const = 0;
|
|
|
223 virtual t_size get_data_size() const = 0;
|
|
|
224 virtual void set_data(const void * p_data,t_size p_data_size) = 0;
|
|
|
225 virtual void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) = 0;
|
|
|
226
|
|
|
227 const dsp_preset & operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
|
|
|
228
|
|
|
229 void copy(const dsp_preset & p_source) {set_owner(p_source.get_owner());set_data(p_source.get_data(),p_source.get_data_size());}
|
|
|
230
|
|
|
231 void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
|
|
|
232 void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
|
|
|
233 static void g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort);
|
|
|
234
|
|
|
235 bool operator==(const dsp_preset & p_other) const {
|
|
|
236 if (get_owner() != p_other.get_owner()) return false;
|
|
|
237 if (get_data_size() != p_other.get_data_size()) return false;
|
|
|
238 if (memcmp(get_data(),p_other.get_data(),get_data_size()) != 0) return false;
|
|
|
239 return true;
|
|
|
240 }
|
|
|
241 bool operator!=(const dsp_preset & p_other) const {
|
|
|
242 return !(*this == p_other);
|
|
|
243 }
|
|
|
244
|
|
|
245 pfc::string8 get_owner_name() const;
|
|
|
246 pfc::string8 get_owner_name_debug() const;
|
|
|
247 pfc::string8 debug(const char * knownName = nullptr) const;
|
|
|
248 protected:
|
|
|
249 dsp_preset() {}
|
|
|
250 ~dsp_preset() {}
|
|
|
251 };
|
|
|
252
|
|
|
253 class dsp_preset_writer : public stream_writer {
|
|
|
254 public:
|
|
|
255 void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
|
|
256 p_abort.check();
|
|
|
257 m_data.append_fromptr((const t_uint8 *) p_buffer,p_bytes);
|
|
|
258 }
|
|
|
259 void flush(dsp_preset & p_preset) {
|
|
|
260 p_preset.set_data(m_data.get_ptr(),m_data.get_size());
|
|
|
261 m_data.set_size(0);
|
|
|
262 }
|
|
|
263 private:
|
|
|
264 pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> m_data;
|
|
|
265 };
|
|
|
266
|
|
|
267 class dsp_preset_reader : public stream_reader {
|
|
|
268 public:
|
|
|
269 dsp_preset_reader() : m_walk(0) {}
|
|
|
270 dsp_preset_reader(const dsp_preset_reader & p_source) : m_walk(0) {*this = p_source;}
|
|
|
271 void init(const dsp_preset & p_preset) {
|
|
|
272 m_data.set_data_fromptr( (const t_uint8*) p_preset.get_data(), p_preset.get_data_size() );
|
|
|
273 m_walk = 0;
|
|
|
274 }
|
|
|
275 t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
|
|
276 p_abort.check();
|
|
|
277 t_size todo = pfc::min_t<t_size>(p_bytes,m_data.get_size()-m_walk);
|
|
|
278 memcpy(p_buffer,m_data.get_ptr()+m_walk,todo);
|
|
|
279 m_walk += todo;
|
|
|
280 return todo;
|
|
|
281 }
|
|
|
282 bool is_finished() {return m_walk == m_data.get_size();}
|
|
|
283 private:
|
|
|
284 t_size m_walk;
|
|
|
285 pfc::array_t<t_uint8> m_data;
|
|
|
286 };
|
|
|
287
|
|
|
288 class dsp_preset_impl : public dsp_preset
|
|
|
289 {
|
|
|
290 public:
|
|
|
291 dsp_preset_impl() {}
|
|
|
292 dsp_preset_impl(const dsp_preset_impl & p_source) {copy(p_source);}
|
|
|
293 dsp_preset_impl(const dsp_preset & p_source) {copy(p_source);}
|
|
|
294 dsp_preset_impl(dsp_preset_impl && p_source) noexcept {move(p_source);}
|
|
|
295 void clear() {m_owner = pfc::guid_null; m_data.clear();}
|
|
|
296 bool is_valid() const { return m_owner != pfc::guid_null; }
|
|
|
297
|
|
|
298 const dsp_preset_impl& operator=(const dsp_preset_impl & p_source) {copy(p_source); return *this;}
|
|
|
299 const dsp_preset_impl& operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
|
|
|
300 const dsp_preset_impl& operator=(dsp_preset_impl&& p_source) noexcept { move(p_source); return *this; }
|
|
|
301
|
|
|
302 GUID get_owner() const {return m_owner;}
|
|
|
303 void set_owner(const GUID & p_owner) {m_owner = p_owner;}
|
|
|
304 const void * get_data() const {return m_data.ptr();}
|
|
|
305 t_size get_data_size() const {return m_data.size();}
|
|
|
306 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);}
|
|
|
307 void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort);
|
|
|
308
|
|
|
309 void move(dsp_preset_impl& source) noexcept {
|
|
|
310 m_owner = source.m_owner;
|
|
|
311 m_data = std::move(source.m_data);
|
|
|
312 }
|
|
|
313
|
|
|
314 void set_data(pfc::mem_block&& data) { m_data = std::move(data); }
|
|
|
315 private:
|
|
|
316 GUID m_owner = {};
|
|
|
317 pfc::mem_block m_data;
|
|
|
318 };
|
|
|
319
|
|
|
320 class NOVTABLE dsp_preset_edit_callback {
|
|
|
321 public:
|
|
|
322 virtual void on_preset_changed(const dsp_preset &) = 0;
|
|
|
323 private:
|
|
|
324 dsp_preset_edit_callback(const dsp_preset_edit_callback&) = delete;
|
|
|
325 const dsp_preset_edit_callback & operator=(const dsp_preset_edit_callback &) = delete;
|
|
|
326 protected:
|
|
|
327 dsp_preset_edit_callback() {}
|
|
|
328 ~dsp_preset_edit_callback() {}
|
|
|
329 };
|
|
|
330
|
|
|
331 class NOVTABLE dsp_preset_edit_callback_v2 : public service_base {
|
|
|
332 FB2K_MAKE_SERVICE_INTERFACE(dsp_preset_edit_callback_v2, service_base);
|
|
|
333 public:
|
|
|
334 virtual void get_preset( dsp_preset & outPreset ) = 0;
|
|
|
335 virtual void set_preset( const dsp_preset & inPreset ) = 0;
|
|
|
336 virtual void dsp_dialog_done( bool bOK ) = 0;
|
|
|
337 void reset();
|
|
|
338 };
|
|
|
339
|
|
|
340
|
|
|
341 class NOVTABLE dsp_entry : public service_base {
|
|
|
342 public:
|
|
|
343 virtual void get_name(pfc::string_base & p_out) = 0;
|
|
|
344 virtual bool get_default_preset(dsp_preset & p_out) = 0;
|
|
|
345 virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;
|
|
|
346 virtual GUID get_guid() = 0;
|
|
|
347 virtual bool have_config_popup() = 0;
|
|
|
348
|
|
|
349 #ifdef FOOBAR2000_MOBILE
|
|
|
350 virtual void show_config_popup( fb2k::dspConfigContext_t parent, dsp_preset_edit_callback_v2::ptr callback ) {}
|
|
|
351 #endif
|
|
|
352
|
|
|
353 #ifdef FOOBAR2000_DESKTOP
|
|
|
354 #ifdef _WIN32
|
|
|
355 //! Shows configuration popup. Call from main thread only! \n
|
|
|
356 //! Blocks until done. Returns true if preset has been altered, false otherwise.
|
|
|
357 //! Legacy method, replaced in dsp_entry_v2 and newer.
|
|
|
358 virtual bool show_config_popup(dsp_preset& p_data, fb2k::hwnd_t p_parent) { (void)p_data; (void)p_parent; return false; }
|
|
|
359 #else // non-Windows desktop
|
|
|
360 //! Shows configuration popup. Main thread only! \n
|
|
|
361 //! Mac: returns NSObjectWrapper holding NSViewController
|
|
|
362 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(); }
|
|
|
363 #endif
|
|
|
364 #endif // FOOBAR2000_DESKTOP
|
|
|
365
|
|
|
366 //! Obsolete method, hidden DSPs now use a different entry class.
|
|
|
367 bool is_user_accessible() { return true; }
|
|
|
368
|
|
|
369 static constexpr unsigned flag_playback = 1 << 0,
|
|
|
370 flag_conversion = 1 << 1;
|
|
|
371
|
|
|
372 static bool g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid);
|
|
|
373 static service_ptr_t<dsp_entry> g_get_interface(const GUID&);
|
|
|
374 static bool g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset, unsigned flags = 0);
|
|
|
375 static bool g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid);
|
|
|
376 static bool g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid);
|
|
|
377 static bool g_dsp_exists(const GUID & p_guid);
|
|
|
378 static bool g_get_default_preset(dsp_preset & p_out,const GUID & p_guid);
|
|
|
379 static bool g_have_config_popup(const GUID & p_guid);
|
|
|
380 static bool g_have_config_popup(const dsp_preset & p_preset);
|
|
|
381 #ifdef _WIN32
|
|
|
382 //! Shows configuration popup. Main thread only! \n
|
|
|
383 //! Blocks until done. Returns true if preset has been altered, false otherwise.
|
|
|
384 static bool g_show_config_popup(dsp_preset & p_preset,fb2k::hwnd_t p_parent);
|
|
|
385 //! Shows configuration popup. Main thread only!
|
|
|
386 //! Blocks until done. Uses callback to notify host about preset change.
|
|
|
387 static void g_show_config_popup_v2(const dsp_preset & p_preset,fb2k::hwnd_t p_parent,dsp_preset_edit_callback & p_callback);
|
|
|
388
|
|
|
389 //! Shows configuration popup. Main thread only! \n
|
|
|
390 //! Blocks until done. Uses callback to notify host about preset change. \n
|
|
|
391 //! Implements a fallback using legacy methods if show_config_popup_v2() is not available. \n
|
|
|
392 //! @returns OK/cancel status (true/false), if the dialog supports it; otherwise always true.
|
|
|
393 bool show_config_popup_v2_(const dsp_preset& p_preset, fb2k::hwnd_t p_parent, dsp_preset_edit_callback& p_callback);
|
|
|
394
|
|
|
395 //! Shows configuration popup. Main thread only! \n
|
|
|
396 //! May either block until done and return null, or run asynchronously and return an object to release to cancel the dialog. \n
|
|
|
397 //! Implements a fallback using legacy methods if show_config_popup_v3() is not available.
|
|
|
398 service_ptr show_config_popup_v3_(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback);
|
|
|
399 #endif
|
|
|
400
|
|
|
401 #ifdef FOOBAR2000_MOBILE
|
|
|
402 static void g_show_config_popup( fb2k::dspConfigContext_t parent, dsp_preset_edit_callback_v2::ptr callback);
|
|
|
403 #endif
|
|
|
404
|
|
|
405 bool get_display_name_supported();
|
|
|
406 void get_display_name_(const dsp_preset& arg, pfc::string_base& out);
|
|
|
407 bool enumerate_default_presets_(dsp_chain_config& ret);
|
|
|
408 bool match_preset_subclass_(dsp_preset const& x, dsp_preset const& y);
|
|
|
409
|
|
|
410 FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry);
|
|
|
411 };
|
|
|
412
|
|
|
413 class NOVTABLE dsp_entry_v2 : public dsp_entry {
|
|
|
414 public:
|
|
|
415 #ifdef _WIN32
|
|
|
416 //! Shows configuration popup. Main thread only!
|
|
|
417 virtual void show_config_popup_v2(const dsp_preset & p_data,fb2k::hwnd_t p_parent,dsp_preset_edit_callback & p_callback) = 0;
|
|
|
418 // Obsolete method, redirected to show_config_popup_v2() by default, no need to implement.
|
|
|
419 bool show_config_popup(dsp_preset& p_data, fb2k::hwnd_t p_parent) override;
|
|
|
420 #endif
|
|
|
421 private:
|
|
|
422
|
|
|
423 FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v2,dsp_entry);
|
|
|
424 };
|
|
|
425
|
|
|
426 //! \since Late 1.6.x
|
|
|
427 class NOVTABLE dsp_entry_v3 : public dsp_entry_v2 {
|
|
|
428 FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v3, dsp_entry_v2);
|
|
|
429 public:
|
|
|
430 //! Returns the text to show in DSP list, for this specific preset.
|
|
|
431 virtual void get_display_name(const dsp_preset& arg, pfc::string_base& out) = 0;
|
|
|
432
|
|
|
433 #ifdef _WIN32
|
|
|
434 //! Shows configuration popup, asynchronous version - creates dialog then returns immediately. \n
|
|
|
435 //! Since not every DSP implements this, caller must be prepared to call legacy blocking show_config_popup methods instead. \n
|
|
|
436 //! show_config_popup_v3() may throw pfc::exception_not_implemented() to signal host that this DSP doesn't support this method yet. \n
|
|
|
437 //! Main thread only! \n
|
|
|
438 //! @returns Object to retain by host, to be released to request the dialog to be closed.
|
|
|
439 virtual service_ptr show_config_popup_v3(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback) = 0;
|
|
|
440 #endif
|
|
|
441 };
|
|
|
442
|
|
|
443 //! \since 2.1
|
|
|
444 class NOVTABLE dsp_entry_v4 : public dsp_entry_v3 {
|
|
|
445 FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v4, dsp_entry_v3);
|
|
|
446 public:
|
|
|
447 virtual dsp::ptr instantiate_v4( const dsp_preset & arg, unsigned flags ) = 0;
|
|
|
448 };
|
|
|
449
|
|
|
450 //! \since 2.2
|
|
|
451 class NOVTABLE dsp_entry_v5 : public dsp_entry_v4 {
|
|
|
452 FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v5, dsp_entry_v4);
|
|
|
453 public:
|
|
|
454 //! If your DSP implementation is meant to preset as multiple item in available DSP list, implement this method. \n
|
|
|
455 //! @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.
|
|
|
456 virtual bool enumerate_default_presets(dsp_chain_config& ret) { (void)ret; return false; }
|
|
|
457 //! Can possibly reach state Y by editing state X, and vice versa? \n
|
|
|
458 //! If this DSP has no configuration UI, this should just test if the presets are identical.
|
|
|
459 //! Frontend will use this to pin running presets to one of available DSP list items.
|
|
|
460 virtual bool match_preset_subclass(dsp_preset const& x, dsp_preset const& y) { (void)x; (void)y; return true; }
|
|
|
461 };
|
|
|
462
|
|
|
463 class NOVTABLE dsp_entry_hidden : public service_base {
|
|
|
464 FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry_hidden);
|
|
|
465 public:
|
|
|
466 //! Obsolete method, hidden DSPs now use a different entry class from ordinary ones.
|
|
|
467 bool is_user_accessible() {return false; }
|
|
|
468
|
|
|
469 static bool g_get_interface( dsp_entry_hidden::ptr & out, const GUID & guid );
|
|
|
470 static bool g_instantiate( dsp::ptr & out, const dsp_preset & preset );
|
|
|
471 static bool g_dsp_exists(const GUID & p_guid);
|
|
|
472
|
|
|
473 virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;
|
|
|
474 virtual GUID get_guid() = 0;
|
|
|
475 };
|
|
|
476
|
|
|
477 template<class T,class t_entry = dsp_entry>
|
|
|
478 class dsp_entry_impl_nopreset_t : public t_entry {
|
|
|
479 public:
|
|
|
480 void get_name(pfc::string_base & p_out) override {T::g_get_name(p_out);}
|
|
|
481 bool get_default_preset(dsp_preset & p_out) override
|
|
|
482 {
|
|
|
483 p_out.set_owner(T::g_get_guid());
|
|
|
484 p_out.set_data(0,0);
|
|
|
485 return true;
|
|
|
486 }
|
|
|
487 bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) override
|
|
|
488 {
|
|
|
489 if (p_preset.get_owner() == T::g_get_guid() && p_preset.get_data_size() == 0)
|
|
|
490 {
|
|
|
491 p_out = new service_impl_t<T>();
|
|
|
492 return p_out.is_valid();
|
|
|
493 }
|
|
|
494 else return false;
|
|
|
495 }
|
|
|
496 GUID get_guid() override {return T::g_get_guid();}
|
|
|
497
|
|
|
498 bool have_config_popup() override {return false;}
|
|
|
499 };
|
|
|
500
|
|
|
501 template<typename T, typename interface_t>
|
|
|
502 class dsp_entry_common_t : public interface_t {
|
|
|
503 public:
|
|
|
504 void get_name(pfc::string_base& p_out) override { T::g_get_name(p_out); }
|
|
|
505 bool get_default_preset(dsp_preset& p_out) override { return T::g_get_default_preset(p_out); }
|
|
|
506 bool instantiate(service_ptr_t<dsp>& p_out, const dsp_preset& p_preset) override {
|
|
|
507 if (p_preset.get_owner() == T::g_get_guid()) {
|
|
|
508 p_out = new service_impl_t<T>(p_preset);
|
|
|
509 return true;
|
|
|
510 } else return false;
|
|
|
511 }
|
|
|
512 GUID get_guid() override { return T::g_get_guid(); }
|
|
|
513
|
|
|
514 bool have_config_popup() override { return T::g_have_config_popup(); }
|
|
|
515 #ifdef FOOBAR2000_MOBILE
|
|
|
516 void show_config_popup( fb2k::dspConfigContext_t parent, dsp_preset_edit_callback_v2::ptr callback ) override { T::g_show_config_popup(parent, callback); }
|
|
|
517 #endif
|
|
|
518 #if defined(FOOBAR2000_DESKTOP) && !defined(_WIN32)
|
|
|
519 service_ptr show_config_popup( fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback ) override {
|
|
|
520 return T::g_show_config_popup(parent, callback);
|
|
|
521 }
|
|
|
522 #endif
|
|
|
523 };
|
|
|
524
|
|
|
525 template<class T, class t_entry = dsp_entry>
|
|
|
526 class dsp_entry_impl_t : public dsp_entry_common_t<T, t_entry> {
|
|
|
527 public:
|
|
|
528 #ifdef _WIN32
|
|
|
529 bool show_config_popup(dsp_preset & p_data,fb2k::hwnd_t p_parent) override {return T::g_show_config_popup(p_data,p_parent);}
|
|
|
530 #endif
|
|
|
531 };
|
|
|
532
|
|
|
533 template<class T, class t_entry = dsp_entry_v2>
|
|
|
534 class dsp_entry_v2_impl_t : public dsp_entry_common_t<T, t_entry> {
|
|
|
535 public:
|
|
|
536 #ifdef _WIN32
|
|
|
537 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);}
|
|
|
538 #endif
|
|
|
539 };
|
|
|
540
|
|
|
541 template<class T, class t_entry = dsp_entry_v3>
|
|
|
542 class dsp_entry_v3_impl_t : public dsp_entry_v2_impl_t<T, t_entry> {
|
|
|
543 public:
|
|
|
544 void get_display_name(const dsp_preset& arg, pfc::string_base& out) override {
|
|
|
545 T::g_get_display_name(arg, out);
|
|
|
546 }
|
|
|
547
|
|
|
548 #ifdef _WIN32
|
|
|
549 service_ptr show_config_popup_v3(fb2k::hwnd_t parent, dsp_preset_edit_callback_v2::ptr callback) override {
|
|
|
550 return T::g_show_config_popup_v3(parent, callback);
|
|
|
551 }
|
|
|
552 #endif
|
|
|
553 };
|
|
|
554
|
|
|
555 template<class dsp_t, class entry_t = dsp_entry_v4>
|
|
|
556 class dsp_entry_v4_impl_t : public dsp_entry_v3_impl_t< dsp_t, entry_t > {
|
|
|
557 public:
|
|
|
558 dsp::ptr instantiate_v4( const dsp_preset & arg, unsigned flags ) override {
|
|
|
559 PFC_ASSERT( arg.get_owner() == dsp_t::g_get_guid() );
|
|
|
560 return new service_impl_t< dsp_t > ( arg, flags );
|
|
|
561 }
|
|
|
562 };
|
|
|
563
|
|
|
564 template<class dsp_t, class entry_t = dsp_entry_v5>
|
|
|
565 class dsp_entry_v5_impl_t : public dsp_entry_v4_impl_t< dsp_t, entry_t > {
|
|
|
566 public:
|
|
|
567 bool enumerate_default_presets(dsp_chain_config& ret) {
|
|
|
568 return dsp_t::g_enumerate_default_presets(ret);
|
|
|
569 }
|
|
|
570 bool match_preset_subclass(dsp_preset const& x, dsp_preset const& y) {
|
|
|
571 return dsp_t::g_match_preset_subclass(x, y);
|
|
|
572 }
|
|
|
573 };
|
|
|
574
|
|
|
575 template<typename dsp_t>
|
|
|
576 class dsp_entry_hidden_t : public dsp_entry_hidden {
|
|
|
577 public:
|
|
|
578 bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) override {
|
|
|
579 if (p_preset.get_owner() == dsp_t::g_get_guid()) {
|
|
|
580 p_out = new service_impl_t<dsp_t>(p_preset);
|
|
|
581 return true;
|
|
|
582 } else return false;
|
|
|
583 }
|
|
|
584 GUID get_guid() override {return dsp_t::g_get_guid();}
|
|
|
585 };
|
|
|
586
|
|
|
587 template<typename dsp_t, typename interface_t>
|
|
|
588 class implement_dsp_entry;
|
|
|
589
|
|
|
590 template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry> : public dsp_entry_impl_t<dsp_t> {};
|
|
|
591 template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v2> : public dsp_entry_v2_impl_t<dsp_t> {};
|
|
|
592 template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v3> : public dsp_entry_v3_impl_t<dsp_t> {};
|
|
|
593 template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v4> : public dsp_entry_v4_impl_t<dsp_t> {};
|
|
|
594 template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_v5> : public dsp_entry_v5_impl_t<dsp_t> {};
|
|
|
595 template<typename dsp_t> class implement_dsp_entry<dsp_t, dsp_entry_hidden> : public dsp_entry_hidden_t<dsp_t> {};
|
|
|
596
|
|
|
597 template<class T>
|
|
|
598 class dsp_factory_nopreset_t : public service_factory_single_t<dsp_entry_impl_nopreset_t<T> > {};
|
|
|
599
|
|
|
600 template<typename dsp_t, typename interface_t = dsp_entry_v2>
|
|
|
601 class dsp_factory_t : public service_factory_single_t<implement_dsp_entry<dsp_t, interface_t> > {};
|
|
|
602
|
|
|
603 template<class T>
|
|
|
604 class dsp_factory_hidden_t : public service_factory_single_t< dsp_entry_hidden_t<T> > {};
|
|
|
605
|
|
|
606 class NOVTABLE dsp_chain_config
|
|
|
607 {
|
|
|
608 public:
|
|
|
609 virtual t_size get_count() const = 0;
|
|
|
610 virtual const dsp_preset & get_item(t_size p_index) const = 0;
|
|
|
611 virtual void replace_item(const dsp_preset & p_data,t_size p_index) = 0;
|
|
|
612 virtual void insert_item(const dsp_preset & p_data,t_size p_index) = 0;
|
|
|
613 virtual void remove_mask(const bit_array & p_mask) = 0;
|
|
|
614
|
|
|
615 void remove_item(t_size p_index);
|
|
|
616 void remove_all();
|
|
|
617 void add_item(const dsp_preset & p_data);
|
|
|
618 void copy(const dsp_chain_config & p_source);
|
|
|
619 void add_items(const dsp_chain_config & p_source);
|
|
|
620
|
|
|
621 const dsp_chain_config & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
|
|
|
622
|
|
|
623 void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
|
|
|
624 void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
|
|
|
625 fb2k::memBlock::ptr to_blob() const;
|
|
|
626 void from_blob(const void* p, size_t size);
|
|
|
627 void from_blob(fb2k::memBlock::ptr);
|
|
|
628
|
|
|
629 pfc::string8 get_name_list() const;
|
|
|
630 void get_name_list(pfc::string_base & p_out) const;
|
|
|
631
|
|
|
632 static bool equals(dsp_chain_config const & v1, dsp_chain_config const & v2);
|
|
|
633 static bool equals_debug(dsp_chain_config const& v1, dsp_chain_config const& v2);
|
|
|
634
|
|
|
635 //! Helpers to enable/disable specific DSP in this chain. Return true if the chain has been altered, false otherwise.
|
|
|
636 bool enable_dsp( const GUID & dspID );
|
|
|
637 //! Helpers to enable/disable specific DSP in this chain. Return true if the chain has been altered, false otherwise.
|
|
|
638 bool disable_dsp( const GUID & dspID );
|
|
|
639 //! Helpers to enable/disable specific DSP in this chain. Return true if the chain has been altered, false otherwise.
|
|
|
640 bool enable_dsp( const dsp_preset & preset );
|
|
|
641
|
|
|
642 size_t find_first_of_type( const GUID & dspID ) const;
|
|
|
643 bool contains_dsp( const GUID & dspID ) const;
|
|
|
644
|
|
|
645 pfc::string8 debug() const;
|
|
|
646
|
|
|
647 bool operator==(const dsp_chain_config & other) const {return equals(*this, other);}
|
|
|
648 bool operator!=(const dsp_chain_config & other) const {return !equals(*this, other);}
|
|
|
649 };
|
|
|
650
|
|
|
651 FB2K_STREAM_READER_OVERLOAD(dsp_chain_config) {
|
|
|
652 value.contents_from_stream(&stream.m_stream, stream.m_abort); return stream;
|
|
|
653 }
|
|
|
654
|
|
|
655 FB2K_STREAM_WRITER_OVERLOAD(dsp_chain_config) {
|
|
|
656 value.contents_to_stream(&stream.m_stream, stream.m_abort); return stream;
|
|
|
657 }
|
|
|
658
|
|
|
659 class dsp_chain_config_impl : public dsp_chain_config
|
|
|
660 {
|
|
|
661 public:
|
|
|
662 dsp_chain_config_impl() {}
|
|
|
663 dsp_chain_config_impl(const dsp_chain_config & p_source) {copy(p_source);}
|
|
|
664 dsp_chain_config_impl(const dsp_chain_config_impl & p_source) { copy_v2(p_source);}
|
|
|
665 dsp_chain_config_impl(dsp_chain_config_impl&& p_source) noexcept : m_data(std::move(p_source.m_data)) {}
|
|
|
666 t_size get_count() const override;
|
|
|
667 const dsp_preset & get_item(t_size p_index) const override;
|
|
|
668 void replace_item(const dsp_preset & p_data,t_size p_index) override;
|
|
|
669 void insert_item(const dsp_preset & p_data,t_size p_index) override;
|
|
|
670 void remove_mask(const bit_array & p_mask) override;
|
|
|
671
|
|
|
672 const char* get_dsp_name(size_t idx) const;
|
|
|
673 void insert_item_v2(const dsp_preset& data, const char* dspName, size_t index);
|
|
|
674 void add_item_v2(const dsp_preset& data, const char* dspName);
|
|
|
675 void copy_v2(dsp_chain_config_impl const&);
|
|
|
676 pfc::string8 debug() const;
|
|
|
677
|
|
|
678 const dsp_chain_config_impl & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
|
|
|
679 const dsp_chain_config_impl & operator=(const dsp_chain_config_impl & p_source) {copy_v2(p_source); return *this;}
|
|
|
680 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; }
|
|
|
681
|
|
|
682 ~dsp_chain_config_impl();
|
|
|
683
|
|
|
684 void reorder( const size_t * order, size_t count );
|
|
|
685
|
|
|
686 void supply_name(size_t idx, pfc::string8 && name) { m_data[idx]->dspName = std::move(name); }
|
|
|
687 const char* find_dsp_name(const GUID& guid) const;
|
|
|
688 private:
|
|
|
689
|
|
|
690 struct entry_t {
|
|
|
691 dsp_preset_impl data;
|
|
|
692 pfc::string8 dspName;
|
|
|
693 };
|
|
|
694
|
|
|
695
|
|
|
696 pfc::ptr_list_t<entry_t> m_data;
|
|
|
697 };
|
|
|
698
|
|
|
699 //! Helper.
|
|
|
700 class dsp_preset_parser : public stream_reader_formatter<> {
|
|
|
701 public:
|
|
|
702 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()) {}
|
|
|
703
|
|
|
704 void reset() { _m_stream.reset(); }
|
|
|
705 t_size get_remaining() const { return _m_stream.get_remaining(); }
|
|
|
706
|
|
|
707 void assume_empty() const {
|
|
|
708 if (get_remaining() != 0) throw exception_io_data();
|
|
|
709 }
|
|
|
710
|
|
|
711 GUID get_owner() const { return m_data.get_owner(); }
|
|
|
712 private:
|
|
|
713 const dsp_preset& m_data;
|
|
|
714 stream_reader_memblock_ref _m_stream;
|
|
|
715 };
|
|
|
716
|
|
|
717 //! Helper.
|
|
|
718 class dsp_preset_builder : public stream_writer_formatter<> {
|
|
|
719 public:
|
|
|
720 dsp_preset_builder() : stream_writer_formatter(_m_stream, fb2k::noAbort) {}
|
|
|
721 void finish(const GUID& id, dsp_preset& out) {
|
|
|
722 out.set_owner(id);
|
|
|
723 out.set_data(_m_stream.m_buffer.get_ptr(), _m_stream.m_buffer.get_size());
|
|
|
724 }
|
|
|
725 void reset() {
|
|
|
726 _m_stream.m_buffer.set_size(0);
|
|
|
727 }
|
|
|
728 private:
|
|
|
729 stream_writer_buffer_simple _m_stream;
|
|
|
730 };
|
|
|
731
|
|
|
732 #ifdef __APPLE__
|
|
|
733 // NSObjectWrapper
|
|
|
734 #include "commonObjects-Apple.h"
|
|
|
735 #endif
|
|
|
736
|
|
|
737 #endif
|