1 /*
2  * Hydrogen
3  * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4  *
5  * http://www.hydrogen-music.org
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY, without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 #include <hydrogen/fx/Effects.h>
23 
24 #if defined(H2CORE_HAVE_LADSPA) || _DOXYGEN_
25 
26 #include <hydrogen/Preferences.h>
27 #include <hydrogen/fx/LadspaFX.h>
28 #include <hydrogen/audio_engine.h>
29 #include <hydrogen/helpers/filesystem.h>
30 
31 #include <algorithm>
32 #include <QDir>
33 #include <QLibrary>
34 #include <cassert>
35 
36 #ifdef H2CORE_HAVE_LRDF
37 #include <lrdf.h>
38 #endif
39 
40 using namespace std;
41 
42 namespace H2Core
43 {
44 
45 // static data
46 Effects* Effects::__instance = nullptr;
47 const char* Effects::__class_name = "Effects";
48 
Effects()49 Effects::Effects()
50 		: Object( __class_name )
51 		, m_pRootGroup( nullptr )
52 		, m_pRecentGroup( nullptr )
53 {
54 	__instance = this;
55 
56 	for ( int nFX = 0; nFX < MAX_FX; ++nFX ) {
57 		m_FXList[ nFX ] = nullptr;
58 	}
59 
60 	getPluginList();
61 }
62 
63 
64 
create_instance()65 void Effects::create_instance()
66 {
67 	if ( __instance == nullptr ) {
68 		__instance = new Effects;
69 	}
70 }
71 
72 
73 
74 
~Effects()75 Effects::~Effects()
76 {
77 	//INFOLOG( "DESTROY" );
78 	if ( m_pRootGroup != nullptr ) delete m_pRootGroup;
79 
80 	//INFOLOG( "destroying " + to_string( m_pluginList.size() ) + " LADSPA plugins" );
81 	for ( unsigned i = 0; i < m_pluginList.size(); i++ ) {
82 		delete m_pluginList[i];
83 	}
84 	m_pluginList.clear();
85 
86 	for ( int nFX = 0; nFX < MAX_FX; ++nFX ) {
87 		delete m_FXList[ nFX ];
88 	}
89 }
90 
91 
92 
getLadspaFX(int nFX)93 LadspaFX* Effects::getLadspaFX( int nFX )
94 {
95 	assert( nFX < MAX_FX );
96 	return m_FXList[ nFX ];
97 }
98 
99 
100 
setLadspaFX(LadspaFX * pFX,int nFX)101 void  Effects::setLadspaFX( LadspaFX* pFX, int nFX )
102 {
103 	assert( nFX < MAX_FX );
104 	//INFOLOG( "[setLadspaFX] FX: " + pFX->getPluginLabel() + ", " + to_string( nFX ) );
105 
106 	AudioEngine::get_instance()->lock( RIGHT_HERE );
107 
108 
109 	if ( m_FXList[ nFX ] ) {
110 		( m_FXList[ nFX ] )->deactivate();
111 		delete m_FXList[ nFX ];
112 	}
113 
114 	m_FXList[ nFX ] = pFX;
115 
116 	if ( pFX != nullptr ) {
117 		Preferences::get_instance()->setMostRecentFX( pFX->getPluginName() );
118 		updateRecentGroup();
119 	}
120 
121 
122 	AudioEngine::get_instance()->unlock();
123 }
124 
125 
126 
127 ///
128 /// Loads only usable plugins
129 ///
getPluginList()130 std::vector<LadspaFXInfo*> Effects::getPluginList()
131 {
132 	if ( m_pluginList.size() != 0 ) {
133 		return m_pluginList;
134 	}
135 
136 	foreach ( const QString& sPluginDir, Filesystem::ladspa_paths() ) {
137 		INFOLOG( "*** [getPluginList] reading directory: " + sPluginDir );
138 
139 		QDir dir( sPluginDir );
140 		if ( !dir.exists() ) {
141 			INFOLOG( "Directory " + sPluginDir + " not found" );
142 			continue;
143 		}
144 
145 		QFileInfoList list = dir.entryInfoList();
146 		for ( int i = 0; i < list.size(); ++i ) {
147 			QString sPluginName = list.at( i ).fileName();
148 
149 			if ( ( sPluginName == "." ) || ( sPluginName == ".." ) ) {
150 				continue;
151 			}
152 
153 			// if the file ends with .so or .dll is a plugin, else...
154 #ifdef WIN32
155 			int pos = sPluginName.indexOf( ".dll" );
156 #else
157 #ifdef Q_OS_MACX
158 			int pos = sPluginName.indexOf( ".dylib" );
159 #else
160 			int pos = sPluginName.indexOf( ".so" );
161 #endif
162 #endif
163 			if ( pos == -1 ) {
164 				continue;
165 			}
166 			//warningLog( "[getPluginList] Loading: " + sPluginName  );
167 
168 			QString sAbsPath = QString( "%1/%2" ).arg( sPluginDir ).arg( sPluginName );
169 
170 			QLibrary lib( sAbsPath );
171 			LADSPA_Descriptor_Function desc_func = ( LADSPA_Descriptor_Function )lib.resolve( "ladspa_descriptor" );
172 			if ( desc_func == nullptr ) {
173 				ERRORLOG( "Error loading the library. (" + sAbsPath + ")" );
174 				continue;
175 			}
176 			const LADSPA_Descriptor * d;
177 			if ( desc_func ) {
178 				for ( unsigned i = 0; ( d = desc_func ( i ) ) != nullptr; i++ ) {
179 					LadspaFXInfo* pFX = new LadspaFXInfo( QString::fromLocal8Bit(d->Name) );
180 					pFX->m_sFilename = sAbsPath;
181 					pFX->m_sLabel = QString::fromLocal8Bit(d->Label);
182 					pFX->m_sID = QString::number(d->UniqueID);
183 					pFX->m_sMaker = QString::fromLocal8Bit(d->Maker);
184 					pFX->m_sCopyright = QString::fromLocal8Bit(d->Copyright);
185 
186 					//INFOLOG( "Loading: " + pFX->m_sLabel );
187 
188 					for ( unsigned j = 0; j < d->PortCount; j++ ) {
189 						LADSPA_PortDescriptor pd = d->PortDescriptors[j];
190 						if ( LADSPA_IS_PORT_INPUT( pd ) && LADSPA_IS_PORT_CONTROL( pd ) ) {
191 							pFX->m_nICPorts++;
192 						} else if ( LADSPA_IS_PORT_INPUT( pd ) && LADSPA_IS_PORT_AUDIO( pd ) ) {
193 							pFX->m_nIAPorts++;
194 						} else if ( LADSPA_IS_PORT_OUTPUT( pd ) && LADSPA_IS_PORT_CONTROL( pd ) ) {
195 							pFX->m_nOCPorts++;
196 						} else if ( LADSPA_IS_PORT_OUTPUT( pd ) && LADSPA_IS_PORT_AUDIO( pd ) ) {
197 							pFX->m_nOAPorts++;
198 						} else {
199 //							string sPortName = d->PortNames[ j ];
200 							QString sPortName;
201 							ERRORLOG( QString( "%1::%2 unknown port type" ).arg( pFX->m_sLabel ).arg( sPortName ) );
202 						}
203 					}
204 					if ( ( pFX->m_nIAPorts == 2 ) && ( pFX->m_nOAPorts == 2 ) ) {	// Stereo plugin
205 						m_pluginList.push_back( pFX );
206 					} else if ( ( pFX->m_nIAPorts == 1 ) && ( pFX->m_nOAPorts == 1 ) ) {	// Mono plugin
207 						m_pluginList.push_back( pFX );
208 					} else {	// not supported plugin
209 						//WARNINGLOG( "Plugin not supported: " + sPluginName  );
210 						delete pFX;
211 					}
212 				}
213 			} else {
214 				ERRORLOG( "Error loading: " + sPluginName  );
215 			}
216 		}
217 	}
218 
219 	INFOLOG( QString( "Loaded %1 LADSPA plugins" ).arg( m_pluginList.size() ) );
220 	std::sort( m_pluginList.begin(), m_pluginList.end(), LadspaFXInfo::alphabeticOrder );
221 	return m_pluginList;
222 }
223 
224 
225 
getLadspaFXGroup()226 LadspaFXGroup* Effects::getLadspaFXGroup()
227 {
228 	INFOLOG( "[getLadspaFXGroup]" );
229 
230 //	LadspaFX::getPluginList();	// load the list
231 
232 	if ( m_pRootGroup  ) {
233 		return m_pRootGroup;
234 	}
235 
236 	m_pRootGroup = new LadspaFXGroup( "Root" );
237 
238 	// Adding recent FX.
239 	m_pRecentGroup = new LadspaFXGroup( "Recently Used" );
240 	m_pRootGroup->addChild( m_pRecentGroup );
241 	updateRecentGroup();
242 
243 	LadspaFXGroup *pUncategorizedGroup = new LadspaFXGroup( "Uncategorized" );
244 	m_pRootGroup->addChild( pUncategorizedGroup );
245 
246 	char C = 0;
247 	LadspaFXGroup* pGroup = nullptr;
248 	for ( std::vector<LadspaFXInfo*>::iterator i = m_pluginList.begin(); i < m_pluginList.end(); i++ ) {
249 		char ch = (*i)->m_sName.toLocal8Bit().at(0);
250 		if ( ch != C ) {
251 			C = ch;
252 			pGroup = new LadspaFXGroup( QString( C ) );
253 			pUncategorizedGroup->addChild( pGroup );
254 		}
255 
256 		if(pGroup){
257 			pGroup->addLadspaInfo( *i );
258 		}
259 	}
260 
261 
262 #ifdef H2CORE_HAVE_LRDF
263 	LadspaFXGroup *pLRDFGroup = new LadspaFXGroup( "Categorized(LRDF)" );
264 	m_pRootGroup->addChild( pLRDFGroup );
265 	getRDF( pLRDFGroup, m_pluginList );
266 #endif
267 
268 	return m_pRootGroup;
269 }
270 
updateRecentGroup()271 void Effects::updateRecentGroup()
272 {
273 	if ( m_pRecentGroup == nullptr ) {
274 		return;  // Too early :s
275 	}
276 
277 	m_pRecentGroup->clear();
278 
279 
280 	QString sRecent; // The recent fx names sit in the preferences object
281 	foreach ( sRecent, Preferences::get_instance()->getRecentFX() ) {
282 		for ( std::vector<LadspaFXInfo*>::iterator i = m_pluginList.begin(); i < m_pluginList.end(); i++ ) {
283 			if ( sRecent == (*i)->m_sName ) {
284 				m_pRecentGroup->addLadspaInfo( *i );
285 				break;
286 			}
287 		}
288 	}
289 }
290 
291 #ifdef H2CORE_HAVE_LRDF
292 
293 
getRDF(LadspaFXGroup * pGroup,vector<LadspaFXInfo * > pluginList)294 void Effects::getRDF( LadspaFXGroup *pGroup, vector<LadspaFXInfo*> pluginList )
295 {
296 	lrdf_init();
297 
298 	QString sDir = "/usr/share/ladspa/rdf";
299 
300 	QDir dir( sDir );
301 	if ( !dir.exists() ) {
302 		WARNINGLOG( QString( "Directory %1 not found" ).arg( sDir ) );
303 		return;
304 	}
305 
306 	QFileInfoList list = dir.entryInfoList();
307 	for ( int i = 0; i < list.size(); ++i ) {
308 		QString sFilename = list.at( i ).fileName();
309 		int pos = sFilename.indexOf( ".rdf" );
310 		if ( pos == -1 ) {
311 			continue;
312 		}
313 
314 		QString sRDFFile = QString( "file://%1/%2" ).arg( sDir ).arg( sFilename );
315 
316 		int err = lrdf_read_file( sRDFFile.toLocal8Bit() );
317 		if ( err ) {
318 			ERRORLOG( "Error parsing rdf file " + sFilename );
319 		}
320 
321 		QString sBase = "http://ladspa.org/ontology#Plugin";
322 		RDFDescend( sBase, pGroup, pluginList );
323 	}
324 }
325 
326 
327 
328 // funzione ricorsiva
RDFDescend(const QString & sBase,LadspaFXGroup * pGroup,vector<LadspaFXInfo * > pluginList)329 void Effects::RDFDescend( const QString& sBase, LadspaFXGroup *pGroup, vector<LadspaFXInfo*> pluginList )
330 {
331 	//cout << "LadspaFX::RDFDescend " << sBase.toLocal8Bit().constData() << endl;
332 
333 	lrdf_uris* uris = lrdf_get_subclasses( sBase.toLocal8Bit() );
334 	if ( uris ) {
335 		for ( int i = 0; i < ( int )uris->count; i++ ) {
336 			QString sGroup = QString::fromLocal8Bit(lrdf_get_label( uris->items[ i ] ));
337 
338 			LadspaFXGroup *pNewGroup = nullptr;
339 			// verifico se esiste gia una categoria con lo stesso nome
340 			vector<LadspaFXGroup*> childGroups = pGroup-> getChildList();
341 			for ( unsigned nGroup = 0; nGroup < childGroups.size(); nGroup++ ) {
342 				LadspaFXGroup *pOldGroup = childGroups[nGroup];
343 				if ( pOldGroup->getName() == sGroup ) {
344 					pNewGroup = pOldGroup;
345 					break;
346 				}
347 			}
348 			if ( pNewGroup == nullptr ) {	// il gruppo non esiste, lo creo
349 				pNewGroup = new LadspaFXGroup( sGroup );
350 				pGroup->addChild( pNewGroup );
351 			}
352 			RDFDescend( QString::fromLocal8Bit(uris->items[i]), pNewGroup, pluginList );
353 		}
354 		lrdf_free_uris ( uris );
355 	}
356 
357 	uris = lrdf_get_instances( sBase.toLocal8Bit() );
358 	if ( uris ) {
359 		for ( int i = 0; i < ( int )uris->count; i++ ) {
360 			int uid = lrdf_get_uid ( uris->items[i] );
361 
362 			// verifico che il plugin non sia gia nella lista
363 			bool bExists = false;
364 			vector<LadspaFXInfo*> fxVect = pGroup->getLadspaInfo();
365 			for ( unsigned nFX = 0; nFX < fxVect.size(); nFX++ ) {
366 				LadspaFXInfo *pFX = fxVect[nFX];
367 				if ( pFX->m_sID.toInt() == uid ) {
368 					bExists = true;
369 					continue;
370 				}
371 			}
372 
373 			if ( bExists == false ) {
374 				// find the ladspaFXInfo
375 				for ( unsigned i = 0; i < pluginList.size(); i++ ) {
376 					LadspaFXInfo *pInfo = pluginList[i];
377 
378 					if ( pInfo->m_sID.toInt() == uid  ) {
379 						pGroup->addLadspaInfo( pInfo );	// copy the LadspaFXInfo
380 					}
381 				}
382 			}
383 		}
384 		lrdf_free_uris ( uris );
385 	}
386 	pGroup->sort();
387 }
388 
389 
390 #endif // H2CORE_HAVE_LRDF
391 
392 };
393 
394 #endif // H2CORE_HAVE_LADSPA
395