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
23 #include <hydrogen/basics/drumkit.h>
24 #include <hydrogen/config.h>
25 #ifdef H2CORE_HAVE_LIBARCHIVE
26 #include <archive.h>
27 #include <archive_entry.h>
28 #else
29 #ifndef WIN32
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <zlib.h>
33 #include <libtar.h>
34 #endif
35 #endif
36
37 #include <hydrogen/basics/sample.h>
38 #include <hydrogen/basics/drumkit_component.h>
39 #include <hydrogen/basics/instrument.h>
40 #include <hydrogen/basics/instrument_list.h>
41 #include <hydrogen/basics/instrument_component.h>
42 #include <hydrogen/basics/instrument_layer.h>
43
44 #include <hydrogen/helpers/xml.h>
45 #include <hydrogen/helpers/filesystem.h>
46 #include <hydrogen/helpers/legacy.h>
47
48 namespace H2Core
49 {
50
51 const char* Drumkit::__class_name = "Drumkit";
52
Drumkit()53 Drumkit::Drumkit() : Object( __class_name ), __samples_loaded( false ), __instruments( nullptr ), __components( nullptr )
54 {
55 __components = new std::vector<DrumkitComponent*> ();
56 }
57
Drumkit(Drumkit * other)58 Drumkit::Drumkit( Drumkit* other ) :
59 Object( __class_name ),
60 __path( other->get_path() ),
61 __name( other->get_name() ),
62 __author( other->get_author() ),
63 __info( other->get_info() ),
64 __license( other->get_license() ),
65 __image( other->get_image() ),
66 __imageLicense( other->get_image_license() ),
67 __samples_loaded( other->samples_loaded() ),
68 __components( nullptr )
69 {
70 __instruments = new InstrumentList( other->get_instruments() );
71
72 __components = new std::vector<DrumkitComponent*> ();
73 for (auto it = other->get_components()->begin(); it != other->get_components()->end(); ++it) {
74 __components->push_back(new DrumkitComponent(*it));
75 }
76 }
77
~Drumkit()78 Drumkit::~Drumkit()
79 {
80 for (std::vector<DrumkitComponent*>::iterator it = __components->begin() ; it != __components->end(); ++it) {
81 delete *it;
82 }
83 delete __components;
84
85 if( __instruments ) {
86 delete __instruments;
87 }
88 }
89
load_by_name(const QString & dk_name,const bool load_samples)90 Drumkit* Drumkit::load_by_name ( const QString& dk_name, const bool load_samples )
91 {
92 QString dir = Filesystem::drumkit_path_search( dk_name );
93 if ( dir.isEmpty() ) {
94 return nullptr;
95 }
96
97 return load( dir, load_samples );
98 }
99
load(const QString & dk_dir,const bool load_samples)100 Drumkit* Drumkit::load( const QString& dk_dir, const bool load_samples )
101 {
102 INFOLOG( QString( "Load drumkit %1" ).arg( dk_dir ) );
103 if( !Filesystem::drumkit_valid( dk_dir ) ) {
104 ERRORLOG( QString( "%1 is not valid drumkit" ).arg( dk_dir ) );
105 return nullptr;
106 }
107 return load_file( Filesystem::drumkit_file( dk_dir ), load_samples );
108 }
109
load_file(const QString & dk_path,const bool load_samples)110 Drumkit* Drumkit::load_file( const QString& dk_path, const bool load_samples )
111 {
112 XMLDoc doc;
113 if( !doc.read( dk_path, Filesystem::drumkit_xsd_path() ) ) {
114
115 //Something went wrong. Lets see how old this drumkit is..
116
117 //Do we have any components?
118 doc.read( dk_path );
119 auto nodeList = doc.elementsByTagName( "instrumentComponent" );
120 if( nodeList.size() == 0 )
121 {
122 //No components. That drumkit seems to be quite old. Use legacy code..
123
124 Drumkit* pDrumkit = Legacy::load_drumkit( dk_path );
125 upgrade_drumkit(pDrumkit, dk_path);
126
127 return pDrumkit;
128 } else {
129 //If the drumkit does not comply witht the current xsd, but has components, it may suffer from
130 // problems with invalid values (for example float ADSR values, see #658). Lets try to load it
131 // with our current drumkit.
132
133 XMLNode root = doc.firstChildElement( "drumkit_info" );
134 if ( root.isNull() ) {
135 ERRORLOG( "drumkit_info node not found" );
136 return nullptr;
137 }
138
139 Drumkit* pDrumkit = Drumkit::load_from( &root, dk_path.left( dk_path.lastIndexOf( "/" ) ) );
140 upgrade_drumkit(pDrumkit, dk_path);
141
142 if( load_samples ){
143 pDrumkit->load_samples();
144 }
145 }
146 }
147
148 XMLNode root = doc.firstChildElement( "drumkit_info" );
149 if ( root.isNull() ) {
150 ERRORLOG( "drumkit_info node not found" );
151 return nullptr;
152 }
153
154 Drumkit* pDrumkit = Drumkit::load_from( &root, dk_path.left( dk_path.lastIndexOf( "/" ) ) );
155 if( load_samples ){
156 pDrumkit->load_samples();
157 }
158 return pDrumkit;
159 }
160
load_from(XMLNode * node,const QString & dk_path)161 Drumkit* Drumkit::load_from( XMLNode* node, const QString& dk_path )
162 {
163 QString drumkit_name = node->read_string( "name", "", false, false );
164 if ( drumkit_name.isEmpty() ) {
165 ERRORLOG( "Drumkit has no name, abort" );
166 return nullptr;
167 }
168
169 Drumkit* pDrumkit = new Drumkit();
170 pDrumkit->__path = dk_path;
171 pDrumkit->__name = drumkit_name;
172 pDrumkit->__author = node->read_string( "author", "undefined author" );
173 pDrumkit->__info = node->read_string( "info", "No information available." );
174 pDrumkit->__license = node->read_string( "license", "undefined license" );
175 pDrumkit->__image = node->read_string( "image", "" );
176 pDrumkit->__imageLicense = node->read_string( "imageLicense", "undefined license" );
177
178 XMLNode componentListNode = node->firstChildElement( "componentList" );
179 if ( ! componentListNode.isNull() ) {
180 XMLNode componentNode = componentListNode.firstChildElement( "drumkitComponent" );
181 while ( ! componentNode.isNull() ) {
182 int id = componentNode.read_int( "id", -1 ); // instrument id
183 QString sName = componentNode.read_string( "name", "" ); // name
184 float fVolume = componentNode.read_float( "volume", 1.0 ); // volume
185 DrumkitComponent* pDrumkitComponent = new DrumkitComponent( id, sName );
186 pDrumkitComponent->set_volume( fVolume );
187
188 pDrumkit->get_components()->push_back(pDrumkitComponent);
189
190 componentNode = componentNode.nextSiblingElement( "drumkitComponent" );
191 }
192 } else {
193 WARNINGLOG( "componentList node not found" );
194 DrumkitComponent* pDrumkitComponent = new DrumkitComponent( 0, "Main" );
195 pDrumkit->get_components()->push_back(pDrumkitComponent);
196 }
197
198 XMLNode instruments_node = node->firstChildElement( "instrumentList" );
199 if ( instruments_node.isNull() ) {
200 WARNINGLOG( "instrumentList node not found" );
201 pDrumkit->set_instruments( new InstrumentList() );
202 } else {
203 pDrumkit->set_instruments( InstrumentList::load_from( &instruments_node, dk_path, drumkit_name ) );
204 }
205 return pDrumkit;
206 }
207
load_samples()208 void Drumkit::load_samples()
209 {
210 INFOLOG( QString( "Loading drumkit %1 instrument samples" ).arg( __name ) );
211 if( !__samples_loaded ) {
212 __instruments->load_samples();
213 __samples_loaded = true;
214 }
215 }
216
upgrade_drumkit(Drumkit * pDrumkit,const QString & dk_path)217 void Drumkit::upgrade_drumkit(Drumkit* pDrumkit, const QString& dk_path)
218 {
219 if(pDrumkit != nullptr)
220 {
221 WARNINGLOG( QString( "ugrade drumkit %1" ).arg( dk_path ) );
222
223 Filesystem::file_copy( dk_path,
224 dk_path + ".bak",
225 false /* do not overwrite existing files */ );
226
227 pDrumkit->save_file( dk_path, true, -1 );
228 }
229 }
230
unload_samples()231 void Drumkit::unload_samples()
232 {
233 INFOLOG( QString( "Unloading drumkit %1 instrument samples" ).arg( __name ) );
234 if( __samples_loaded ) {
235 __instruments->unload_samples();
236 __samples_loaded = false;
237 }
238 }
239
save(const QString & name,const QString & author,const QString & info,const QString & license,const QString & image,const QString & imageLicense,InstrumentList * pInstruments,std::vector<DrumkitComponent * > * pComponents,bool overwrite)240 bool Drumkit::save( const QString& name,
241 const QString& author,
242 const QString& info,
243 const QString& license,
244 const QString& image,
245 const QString& imageLicense,
246 InstrumentList* pInstruments,
247 std::vector<DrumkitComponent*>* pComponents,
248 bool overwrite )
249 {
250 Drumkit* pDrumkit = new Drumkit();
251 pDrumkit->set_name( name );
252 pDrumkit->set_author( author );
253 pDrumkit->set_info( info );
254 pDrumkit->set_license( license );
255
256 // Before storing the absolute path to the image of the drumkit it
257 // has to be checked whether an actual path was supplied. If not,
258 // the construction of QFileInfo will fail.
259 if ( !image.isEmpty() ) {
260 QFileInfo fi( image );
261 pDrumkit->set_path( fi.absolutePath() );
262 pDrumkit->set_image( fi.fileName() );
263 }
264 pDrumkit->set_image_license( imageLicense );
265
266 pDrumkit->set_instruments( new InstrumentList( pInstruments ) ); // FIXME: why must we do that ? there is something weird with updateInstrumentLines
267
268 std::vector<DrumkitComponent*>* pCopiedVector = new std::vector<DrumkitComponent*> ();
269 for (std::vector<DrumkitComponent*>::iterator it = pComponents->begin() ; it != pComponents->end(); ++it) {
270 DrumkitComponent* pSrcComponent = *it;
271 pCopiedVector->push_back( new DrumkitComponent( pSrcComponent ) );
272 }
273 pDrumkit->set_components( pCopiedVector );
274
275 bool ret = pDrumkit->save( overwrite );
276 delete pDrumkit;
277
278 return ret;
279 }
280
user_drumkit_exists(const QString & name)281 bool Drumkit::user_drumkit_exists( const QString& name)
282 {
283 return Filesystem::file_exists( Filesystem::drumkit_file( Filesystem::usr_drumkits_dir() + name ), true /*silent*/ );
284 }
285
save(bool overwrite)286 bool Drumkit::save( bool overwrite )
287 {
288 return save( QString( Filesystem::usr_drumkits_dir() + __name ), overwrite );
289 }
290
save(const QString & dk_dir,bool overwrite)291 bool Drumkit::save( const QString& dk_dir, bool overwrite )
292 {
293 INFOLOG( QString( "Saving drumkit %1 into %2" ).arg( __name ).arg( dk_dir ) );
294 if( !Filesystem::mkdir( dk_dir ) ) {
295 return false;
296 }
297 bool ret = save_samples( dk_dir, overwrite );
298 if ( ret ) {
299 ret = save_file( Filesystem::drumkit_file( dk_dir ), overwrite );
300 }
301 return ret;
302 }
303
save_file(const QString & dk_path,bool overwrite,int component_id)304 bool Drumkit::save_file( const QString& dk_path, bool overwrite, int component_id )
305 {
306 INFOLOG( QString( "Saving drumkit definition into %1" ).arg( dk_path ) );
307 if( !overwrite && Filesystem::file_exists( dk_path, true ) ) {
308 ERRORLOG( QString( "drumkit %1 already exists" ).arg( dk_path ) );
309 return false;
310 }
311 XMLDoc doc;
312 XMLNode root = doc.set_root( "drumkit_info", "drumkit" );
313 save_to( &root, component_id );
314 return doc.write( dk_path );
315 }
316
save_to(XMLNode * node,int component_id)317 void Drumkit::save_to( XMLNode* node, int component_id )
318 {
319 node->write_string( "name", __name );
320 node->write_string( "author", __author );
321 node->write_string( "info", __info );
322 node->write_string( "license", __license );
323 node->write_string( "image", __image );
324 node->write_string( "imageLicense", __imageLicense );
325
326 if( component_id == -1 ) {
327 XMLNode components_node = node->createNode( "componentList" );
328 for (std::vector<DrumkitComponent*>::iterator it = __components->begin() ; it != __components->end(); ++it) {
329 DrumkitComponent* pComponent = *it;
330 pComponent->save_to( &components_node );
331 }
332 }
333 __instruments->save_to( node, component_id );
334 }
335
save_samples(const QString & dk_dir,bool overwrite)336 bool Drumkit::save_samples( const QString& dk_dir, bool overwrite )
337 {
338 INFOLOG( QString( "Saving drumkit %1 samples into %2" ).arg( __name ).arg( dk_dir ) );
339 if( !Filesystem::mkdir( dk_dir ) ) {
340 return false;
341 }
342
343 InstrumentList* pInstrList = get_instruments();
344 for( int i = 0; i < pInstrList->size(); i++ ) {
345 Instrument* pInstrument = ( *pInstrList )[i];
346 for (std::vector<InstrumentComponent*>::iterator it = pInstrument->get_components()->begin() ; it != pInstrument->get_components()->end(); ++it) {
347 InstrumentComponent* pComponent = *it;
348
349 for ( int n = 0; n < InstrumentComponent::getMaxLayers(); n++ ) {
350 InstrumentLayer* pLayer = pComponent->get_layer( n );
351 if( pLayer ) {
352 QString src = pLayer->get_sample()->get_filepath();
353 QString dst = dk_dir + "/" + pLayer->get_sample()->get_filename();
354
355 if( src != dst ) {
356 QString original_dst = dst;
357
358 // If the destination path does not have an extension and there is a dot in the path, hell will break loose. QFileInfo maybe?
359 int insertPosition = original_dst.length();
360 if( original_dst.lastIndexOf(".") > 0 ) {
361 insertPosition = original_dst.lastIndexOf(".");
362 }
363
364 if(overwrite == false) {
365 // If the destination path already exists, try to use basename_1, basename_2, etc. instead of basename.
366 int tries = 0;
367 while( Filesystem::file_exists( dst, true )) {
368 tries++;
369 dst = original_dst;
370 dst.insert( insertPosition, QString("_%1").arg(tries) );
371 }
372 }
373
374 pLayer->get_sample()->set_filename( dst );
375
376 if( !Filesystem::file_copy( src, dst ) ) {
377 return false;
378 }
379 }
380 }
381 }
382 }
383 }
384 if ( !save_image( dk_dir, overwrite ) ) {
385 return false;
386 }
387
388 return true;
389 }
390
save_image(const QString & dk_dir,bool overwrite)391 bool Drumkit::save_image( const QString& dk_dir, bool overwrite )
392 {
393 if ( __image.length() > 0 ) {
394 QString src = __path + "/" + __image;
395 QString dst = dk_dir + "/" + __image;
396 if ( Filesystem::file_exists ( src ) ) {
397 if( !Filesystem::file_copy( src, dst ) ) {
398 ERRORLOG( QString( "Error copying %1 to %2").arg( src ).arg( dst ) );
399 return false;
400 }
401 }
402 }
403 return true;
404 }
405
set_instruments(InstrumentList * instruments)406 void Drumkit::set_instruments( InstrumentList* instruments )
407 {
408 if( __instruments != nullptr ) {
409 delete __instruments;
410 }
411
412 __instruments = instruments;
413 }
414
set_components(std::vector<DrumkitComponent * > * components)415 void Drumkit::set_components( std::vector<DrumkitComponent*>* components )
416 {
417 for (std::vector<DrumkitComponent*>::iterator it = __components->begin() ; it != __components->end(); ++it) {
418 delete *it;
419 }
420
421 delete __components;
422 __components = components;
423 }
424
remove(const QString & dk_name)425 bool Drumkit::remove( const QString& dk_name )
426 {
427 QString dk_dir = Filesystem::drumkit_path_search( dk_name );
428 if( !Filesystem::drumkit_valid( dk_dir ) ) {
429 ERRORLOG( QString( "%1 is not valid drumkit" ).arg( dk_dir ) );
430 return false;
431 }
432 _INFOLOG( QString( "Removing drumkit: %1" ).arg( dk_dir ) );
433 if( !Filesystem::rm( dk_dir, true ) ) {
434 _ERRORLOG( QString( "Unable to remove drumkit: %1" ).arg( dk_dir ) );
435 return false;
436 }
437 return true;
438 }
439
dump()440 void Drumkit::dump()
441 {
442 DEBUGLOG( "Drumkit dump" );
443 DEBUGLOG( " |- Path = " + __path );
444 DEBUGLOG( " |- Name = " + __name );
445 DEBUGLOG( " |- Author = " + __author );
446 DEBUGLOG( " |- Info = " + __info );
447 DEBUGLOG( " |- Image = " + __image );
448 DEBUGLOG( " |- Image = " + __imageLicense );
449
450 DEBUGLOG( " |- Instrument list" );
451 for ( int i=0; i<__instruments->size(); i++ ) {
452 Instrument* instrument = ( *__instruments )[i];
453 DEBUGLOG( QString( " |- (%1 of %2) Name = %3" )
454 .arg( i )
455 .arg( __instruments->size()-1 )
456 .arg( instrument->get_name() )
457 );
458 for (std::vector<InstrumentComponent*>::iterator it = instrument->get_components()->begin() ; it != instrument->get_components()->end(); ++it) {
459 InstrumentComponent* pComponent = *it;
460
461 for ( int j = 0; j < InstrumentComponent::getMaxLayers(); j++ ) {
462 InstrumentLayer* pLayer = pComponent->get_layer( j );
463 if ( pLayer ) {
464 Sample* pSample = pLayer->get_sample();
465 if ( pSample ) {
466 DEBUGLOG( QString( " |- %1 [%2]" ).arg( pSample->get_filepath() ).arg( pSample->is_empty() ) );
467 } else {
468 DEBUGLOG( " |- NULL sample" );
469 }
470 }
471 }
472 }
473 }
474 }
475
install(const QString & path)476 bool Drumkit::install( const QString& path )
477 {
478 _INFOLOG( QString( "Install drumkit %1" ).arg( path ) );
479 #ifdef H2CORE_HAVE_LIBARCHIVE
480 int r;
481 struct archive* arch;
482 struct archive_entry* entry;
483
484 arch = archive_read_new();
485
486 #if ARCHIVE_VERSION_NUMBER < 3000000
487 archive_read_support_compression_all( arch );
488 #else
489 archive_read_support_filter_all( arch );
490 #endif
491
492 archive_read_support_format_all( arch );
493
494 #if ARCHIVE_VERSION_NUMBER < 3000000
495 if ( ( r = archive_read_open_file( arch, path.toLocal8Bit(), 10240 ) ) ) {
496 #else
497 if ( ( r = archive_read_open_filename( arch, path.toLocal8Bit(), 10240 ) ) ) {
498 #endif
499 _ERRORLOG( QString( "archive_read_open_file() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
500 archive_read_close( arch );
501
502 #if ARCHIVE_VERSION_NUMBER < 3000000
503 archive_read_finish( arch );
504 #else
505 archive_read_free( arch );
506 #endif
507
508 return false;
509 }
510 bool ret = true;
511 QString dk_dir = Filesystem::usr_drumkits_dir() + "/";
512 while ( ( r = archive_read_next_header( arch, &entry ) ) != ARCHIVE_EOF ) {
513 if ( r != ARCHIVE_OK ) {
514 _ERRORLOG( QString( "archive_read_next_header() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
515 ret = false;
516 break;
517 }
518 QString np = dk_dir + archive_entry_pathname( entry );
519
520 QByteArray newpath = np.toLocal8Bit();
521
522 archive_entry_set_pathname( entry, newpath.data() );
523 r = archive_read_extract( arch, entry, 0 );
524 if ( r == ARCHIVE_WARN ) {
525 _WARNINGLOG( QString( "archive_read_extract() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
526 } else if ( r != ARCHIVE_OK ) {
527 _ERRORLOG( QString( "archive_read_extract() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
528 ret = false;
529 break;
530 }
531 }
532 archive_read_close( arch );
533
534 #if ARCHIVE_VERSION_NUMBER < 3000000
535 archive_read_finish( arch );
536 #else
537 archive_read_free( arch );
538 #endif
539
540 return ret;
541 #else // H2CORE_HAVE_LIBARCHIVE
542 #ifndef WIN32
543 // GUNZIP
544 QString gzd_name = path.left( path.indexOf( "." ) ) + ".tar";
545 FILE* gzd_file = fopen( gzd_name.toLocal8Bit(), "wb" );
546 gzFile gzip_file = gzopen( path.toLocal8Bit(), "rb" );
547 if ( !gzip_file ) {
548 _ERRORLOG( QString( "Error reading drumkit file: %1" ).arg( path ) );
549 gzclose( gzip_file );
550 fclose( gzd_file );
551 return false;
552 }
553 uchar buf[4096];
554 while ( gzread( gzip_file, buf, 4096 ) > 0 ) {
555 fwrite( buf, sizeof( uchar ), 4096, gzd_file );
556 }
557 gzclose( gzip_file );
558 fclose( gzd_file );
559 // UNTAR
560 TAR* tar_file;
561
562 QByteArray tar_path = gzd_name.toLocal8Bit();
563
564 if ( tar_open( &tar_file, tar_path.data(), NULL, O_RDONLY, 0, TAR_GNU ) == -1 ) {
565 _ERRORLOG( QString( "tar_open(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
566 return false;
567 }
568 bool ret = true;
569 char dst_dir[1024];
570 QString dk_dir = Filesystem::usr_drumkits_dir() + "/";
571 strncpy( dst_dir, dk_dir.toLocal8Bit(), 1024 );
572 if ( tar_extract_all( tar_file, dst_dir ) != 0 ) {
573 _ERRORLOG( QString( "tar_extract_all(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
574 ret = false;
575 }
576 if ( tar_close( tar_file ) != 0 ) {
577 _ERRORLOG( QString( "tar_close(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
578 ret = false;
579 }
580 return ret;
581 #else // WIN32
582 _ERRORLOG( "WIN32 NOT IMPLEMENTED" );
583 return false;
584 #endif
585 #endif
586 }
587
588 };
589
590 /* vim: set softtabstop=4 noexpandtab: */
591