hydrogen 1.2.6
Pattern.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include <core/Basics/Pattern.h>
24
25#include <cassert>
26
27#include <core/Basics/Note.h>
30#include <core/Hydrogen.h>
31
33#include <core/Helpers/Legacy.h>
34
35namespace H2Core
36{
37
38Pattern::Pattern( const QString& name, const QString& info, const QString& category, int length, int denominator )
39 : __length( length )
40 , __denominator( denominator)
41 , __name( name )
42 , __info( info )
43 , __category( category )
44{
45}
46
48 : __length( other->get_length() )
49 , __denominator( other->get_denominator() )
50 , __name( other->get_name() )
51 , __info( other->get_info() )
52 , __category( other->get_category() )
53{
55 __notes.insert( std::make_pair( it->first, new Note( it->second ) ) );
56 }
57}
58
60{
61 for( notes_cst_it_t it=__notes.begin(); it!=__notes.end(); it++ ) {
62 delete it->second;
63 }
64}
65
66Pattern* Pattern::load_file( const QString& sPatternPath,
67 std::shared_ptr<InstrumentList> pInstrumentList,
68 bool bSilent )
69{
70 if ( ! bSilent ) {
71 INFOLOG( QString( "Load pattern %1" ).arg( sPatternPath ) );
72 }
73
74 if ( ! Filesystem::file_readable( sPatternPath, bSilent ) ) {
75 return nullptr;
76 }
77
78 XMLDoc doc;
79 if ( ! doc.read( sPatternPath ) ) {
80 ERRORLOG( QString( "Unable to read pattern [%1]" ).arg( sPatternPath ) );
81 return nullptr;
82 }
83
84 XMLNode rootNode = doc.firstChildElement( "drumkit_pattern" );
85 if ( rootNode.isNull() ) {
86 ERRORLOG( QString( "'drumkit_pattern' node not found in [%1]" )
87 .arg( sPatternPath ) );
88 return nullptr;
89 }
90
91 XMLNode patternNode = rootNode.firstChildElement( "pattern" );
92 if ( patternNode.isNull() ) {
93 ERRORLOG( QString( "'pattern' node not found in [%1]" )
94 .arg( sPatternPath ) );
95 return nullptr;
96 }
97
98 // Legacy formats use `pattern_name` over `name` to store the name of the
99 // pattern.
100 XMLNode legacyPatternNameNode = patternNode.firstChildElement( "pattern_name" );
101 if ( ! legacyPatternNameNode.isNull() ) {
102 // Try former pattern version
103 return Legacy::load_drumkit_pattern( sPatternPath, pInstrumentList );
104 }
105
106 // Check whether the file was created using a newer version of Hydrogen.
107 auto formatVersionNode = patternNode.firstChildElement( "formatVersion" );
108 if ( ! formatVersionNode.isNull() ) {
109 WARNINGLOG( QString( "Pattern file [%1] was created with a more recent version of Hydrogen than the current one!" )
110 .arg( sPatternPath ) );
111 }
112
113 return load_from( &patternNode, pInstrumentList );
114}
115
116Pattern* Pattern::load_from( XMLNode* node, std::shared_ptr<InstrumentList> pInstrumentList, bool bSilent )
117{
118 Pattern* pPattern = new Pattern(
119 node->read_string( "name", nullptr, false, false ),
120 node->read_string( "info", "", false, true ),
121 node->read_string( "category", "unknown", false, true, true ),
122 node->read_int( "size", -1, false, false ),
123 node->read_int( "denominator", 4, false, false )
124 );
125
126 if ( pInstrumentList == nullptr ) {
127 ERRORLOG( "Invalid instrument list provided" );
128 return pPattern;
129 }
130
131 XMLNode note_list_node = node->firstChildElement( "noteList" );
132 if ( !note_list_node.isNull() ) {
133 XMLNode note_node = note_list_node.firstChildElement( "note" );
134 while ( !note_node.isNull() ) {
135 Note* pNote = Note::load_from( &note_node, pInstrumentList, bSilent );
136 assert( pNote );
137 if ( pNote != nullptr ) {
138 pPattern->insert_note( pNote );
139 }
140 note_node = note_node.nextSiblingElement( "note" );
141 }
142 }
143
144 return pPattern;
145}
146
147bool Pattern::save_file( const QString& drumkit_name, const QString& author, const License& license, const QString& pattern_path, bool overwrite ) const
148{
149 INFOLOG( QString( "Saving pattern into %1" ).arg( pattern_path ) );
150 if( !overwrite && Filesystem::file_exists( pattern_path, true ) ) {
151 ERRORLOG( QString( "pattern %1 already exists" ).arg( pattern_path ) );
152 return false;
153 }
154 XMLDoc doc;
155 XMLNode root = doc.set_root( "drumkit_pattern", "drumkit_pattern" );
156 root.write_string( "drumkit_name", drumkit_name );
157 root.write_string( "author", author ); // FIXME this is never loaded back
158 root.write_string( "license", license.getLicenseString() );
159 // FIXME this is never loaded back
160 save_to( &root );
161 return doc.write( pattern_path );
162}
163
164void Pattern::save_to( XMLNode* node, const std::shared_ptr<Instrument> pInstrumentOnly ) const
165{
166 XMLNode pattern_node = node->createNode( "pattern" );
167 pattern_node.write_string( "name", __name );
168 pattern_node.write_string( "info", __info );
169 pattern_node.write_string( "category", __category );
170 pattern_node.write_int( "size", __length );
171 pattern_node.write_int( "denominator", __denominator );
172
173 int nId = ( pInstrumentOnly == nullptr ? -1 : pInstrumentOnly->get_id() );
174
175 XMLNode note_list_node = pattern_node.createNode( "noteList" );
176 for( auto it = __notes.cbegin(); it != __notes.cend(); ++it ) {
177 auto pNote = it->second;
178 if ( pNote != nullptr &&
179 ( pInstrumentOnly == nullptr ||
180 pNote->get_instrument()->get_id() == nId ) ) {
181 XMLNode note_node = note_list_node.createNode( "note" );
182 pNote->save_to( &note_node );
183 }
184 }
185}
186
187Note* Pattern::find_note( int idx_a, int idx_b, std::shared_ptr<Instrument> instrument, Note::Key key, Note::Octave octave, bool strict ) const
188{
189 for( notes_cst_it_t it=__notes.lower_bound( idx_a ); it!=__notes.upper_bound( idx_a ); it++ ) {
190 Note* note = it->second;
191 assert( note );
192 if ( note->match( instrument, key, octave ) ) return note;
193 }
194 if( idx_b==-1 ) return nullptr;
195 for( notes_cst_it_t it=__notes.lower_bound( idx_b ); it!=__notes.upper_bound( idx_b ); it++ ) {
196 Note* note = it->second;
197 assert( note );
198 if ( note->match( instrument, key, octave ) ) return note;
199 }
200 if( strict ) return nullptr;
201 // TODO maybe not start from 0 but idx_b-X
202 for ( int n=0; n<idx_b; n++ ) {
203 for( notes_cst_it_t it=__notes.lower_bound( n ); it!=__notes.upper_bound( n ); it++ ) {
204 Note* note = it->second;
205 assert( note );
206 if ( note->match( instrument, key, octave ) && ( ( idx_b<=note->get_position()+note->get_length() ) && idx_b>=note->get_position() ) ) return note;
207 }
208 }
209 return nullptr;
210}
211
212Note* Pattern::find_note( int idx_a, int idx_b, std::shared_ptr<Instrument> instrument, bool strict ) const
213{
215 for( it=__notes.lower_bound( idx_a ); it!=__notes.upper_bound( idx_a ); it++ ) {
216 Note* note = it->second;
217 assert( note );
218 if ( note->get_instrument() == instrument ) return note;
219 }
220 if( idx_b==-1 ) return nullptr;
221 for( it=__notes.lower_bound( idx_b ); it!=__notes.upper_bound( idx_b ); it++ ) {
222 Note* note = it->second;
223 assert( note );
224 if ( note->get_instrument() == instrument ) return note;
225 }
226 if ( strict ) return nullptr;
227 // TODO maybe not start from 0 but idx_b-X
228 for ( int n=0; n<idx_b; n++ ) {
229 for( it=__notes.lower_bound( n ); it!=__notes.upper_bound( n ); it++ ) {
230 Note* note = it->second;
231 assert( note );
232 if ( note->get_instrument() == instrument && ( ( idx_b<=note->get_position()+note->get_length() ) && idx_b>=note->get_position() ) ) return note;
233 }
234 }
235
236 return nullptr;
237}
238
240{
241 int pos = note->get_position();
242 for( notes_it_t it=__notes.lower_bound( pos ); it!=__notes.end() && it->first == pos; ++it ) {
243 if( it->second==note ) {
244 __notes.erase( it );
245 break;
246 }
247 }
248}
249
250bool Pattern::references( std::shared_ptr<Instrument> instr )
251{
252 for( notes_cst_it_t it=__notes.begin(); it!=__notes.end(); it++ ) {
253 Note* note = it->second;
254 assert( note );
255 if ( note->get_instrument() == instr ) {
256 return true;
257 }
258 }
259 return false;
260}
261
262void Pattern::purge_instrument( std::shared_ptr<Instrument> instr, bool bRequiresLock )
263{
264 bool locked = false;
265 std::list< Note* > slate;
266 for( notes_it_t it=__notes.begin(); it!=__notes.end(); ) {
267 Note* note = it->second;
268 assert( note );
269 if ( note->get_instrument() == instr ) {
270 if ( !locked && bRequiresLock ) {
272 locked = true;
273 }
274 slate.push_back( note );
275 __notes.erase( it++ );
276 } else {
277 ++it;
278 }
279 }
280 if ( locked ) {
282 }
283 while ( slate.size() ) {
284 delete slate.front();
285 slate.pop_front();
286 }
287}
288
289void Pattern::clear( bool bRequiresLock )
290{
291 auto pAudioEngine = Hydrogen::get_instance()->getAudioEngine();
292 if ( bRequiresLock ){
293 pAudioEngine->lock( RIGHT_HERE );
294 }
295 std::list< Note* > slate;
296 for ( notes_it_t it=__notes.begin(); it!=__notes.end(); ) {
297 Note* note = it->second;
298 assert( note );
299 slate.push_back( note );
300 __notes.erase( it++ );
301 }
302 if ( bRequiresLock ) {
303 pAudioEngine->unlock();
304 }
305
306 while ( slate.size() ) {
307 delete slate.front();
308 slate.pop_front();
309 }
310}
311
313{
314 for( notes_cst_it_t it=__notes.begin(); it!=__notes.end(); it++ ) {
315 Note* note = it->second;
316 assert( note );
317 note->set_just_recorded( false );
318 }
319}
320
322{
323 // __flattened_virtual_patterns must have been cleared before
324 if( __flattened_virtual_patterns.size() >= __virtual_patterns.size() ) return;
325 // for each virtual pattern
326 for( virtual_patterns_cst_it_t it0=__virtual_patterns.begin(); it0!=__virtual_patterns.end(); ++it0 ) {
327 __flattened_virtual_patterns.insert( *it0 ); // add it
328 ( *it0 )->flattened_virtual_patterns_compute(); // build it's flattened virtual patterns set
329 // for each pattern of it's flattened virtual pattern set
330 for( virtual_patterns_cst_it_t it1=( *it0 )->get_flattened_virtual_patterns()->begin(); it1!=( *it0 )->get_flattened_virtual_patterns()->end(); ++it1 ) {
331 // add the pattern
332 __flattened_virtual_patterns.insert( *it1 );
333 }
334 }
335}
336
339 it!=__flattened_virtual_patterns.end(); ++it ) {
340 pPatternList->add( *it, true );
341 }
342}
343
346 it!=__flattened_virtual_patterns.end(); ++it ) {
347 pPatternList->del( *it );
348 }
349}
350
352 int nMax = __length;
354 it!=__flattened_virtual_patterns.end(); ++it ) {
355 if ( (*it)->__length > nMax ) {
356 nMax = (*it)->__length;
357 }
358 }
359
360 return nMax;
361}
362
363bool Pattern::isVirtual() const {
364 return __flattened_virtual_patterns.size() > 0;
365}
366
367std::set<Pattern*>::iterator Pattern::begin() {
368 return __flattened_virtual_patterns.begin();
369}
370
371std::set<Pattern*>::iterator Pattern::end() {
372 return __flattened_virtual_patterns.end();
373}
374
375QString Pattern::toQString( const QString& sPrefix, bool bShort ) const {
376 QString s = Base::sPrintIndention;
377 QString sOutput;
378 if ( ! bShort ) {
379 sOutput = QString( "%1[Pattern]\n" ).arg( sPrefix )
380 .append( QString( "%1%2length: %3\n" ).arg( sPrefix ).arg( s ).arg( __length ) )
381 .append( QString( "%1%2denominator: %3\n" ).arg( sPrefix ).arg( s ).arg( __denominator ) )
382 .append( QString( "%1%2name: %3\n" ).arg( sPrefix ).arg( s ).arg( __name ) )
383 .append( QString( "%1%2category: %3\n" ).arg( sPrefix ).arg( s ).arg( __category ) )
384 .append( QString( "%1%2info: %3\n" ).arg( sPrefix ).arg( s ).arg( __info ) )
385 .append( QString( "%1%2Notes:\n" ).arg( sPrefix ).arg( s ) );
386
387 for ( auto it = __notes.begin(); it != __notes.end(); it++ ) {
388 if ( it->second != nullptr ) {
389 sOutput.append( QString( "%1" ).arg( it->second->toQString( sPrefix + s + s, bShort ) ) );
390 }
391 }
392
393 sOutput.append( QString( "%1%2Virtual_patterns:\n" ).arg( sPrefix ).arg( s ) );
394 for ( auto ii : __virtual_patterns ) {
395 if ( ii != nullptr ) {
396 sOutput.append( QString( "%1" ).arg( ii->toQString( sPrefix + s + s, bShort ) ) );
397 }
398 }
399
400 sOutput.append( QString( "%1%2Flattened_virtual_patterns:\n" ).arg( sPrefix ).arg( s ) );
401 for ( auto ii : __flattened_virtual_patterns ) {
402 if ( ii != nullptr ) {
403 sOutput.append( QString( "%1" ).arg( ii->toQString( sPrefix + s + s, bShort ) ) );
404 }
405 }
406 } else {
407
408 sOutput = QString( "[Pattern]" )
409 .append( QString( " length: %1" ).arg( __length ) )
410 .append( QString( ", denominator: %1" ).arg( __denominator ) )
411 .append( QString( ", name: %1" ).arg( __name ) )
412 .append( QString( ", category: %1" ).arg( __category ) )
413 .append( QString( ", info: %1" ).arg( __info ) )
414 .append( QString( ", [Notes: " ) );
415 for ( auto it = __notes.begin(); it != __notes.end(); it++ ) {
416 if ( it->second != nullptr ) {
417 sOutput.append( QString( "[%2, %3] " )
418 .arg( it->second->get_instrument()->get_name() )
419 .arg( it->second->get_position() ) );
420 }
421 }
422 sOutput.append( "]" );
423 if ( __virtual_patterns.size() != 0 ) {
424 sOutput.append( ", Virtual_patterns: {" );
425 }
426 for ( auto ii : __virtual_patterns ) {
427 if ( ii != nullptr ) {
428 sOutput.append( QString( "%1" ).arg( ii->toQString( sPrefix + s + s, bShort ) ) );
429 }
430 }
431 if ( __flattened_virtual_patterns.size() != 0 ) {
432 sOutput.append( "}, Flattened_virtual_patterns: {" );
433 }
434 for ( auto ii : __flattened_virtual_patterns ) {
435 if ( ii != nullptr ) {
436 sOutput.append( QString( "%1" ).arg( ii->toQString( sPrefix + s + s, bShort ) ) );
437 }
438 }
439 if ( __flattened_virtual_patterns.size() != 0 ) {
440 sOutput.append( "}" );
441 }
442 }
443 return sOutput;
444}
445
446};
447
448/* vim: set softtabstop=4 noexpandtab: */
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
Definition AudioEngine.h:61
#define INFOLOG(x)
Definition Object.h:240
#define WARNINGLOG(x)
Definition Object.h:241
#define ERRORLOG(x)
Definition Object.h:242
#define FOREACH_NOTE_CST_IT_BEGIN_END(_notes, _it)
Iterate over all provided notes in an immutable way.
Definition Pattern.h:268
void unlock()
Mutex unlocking of the AudioEngine.
void lock(const char *file, unsigned int line, const char *function)
Mutex locking of the AudioEngine.
static QString sPrintIndention
String used to format the debugging string output of some core classes.
Definition Object.h:127
static bool file_exists(const QString &path, bool silent=false)
returns true if the given path is an existing regular file
static bool file_readable(const QString &path, bool silent=false)
returns true if the given path is an existing readable regular file
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
static Pattern * load_drumkit_pattern(const QString &pattern_path, std::shared_ptr< InstrumentList > instrList)
load pattern from a file
Definition Legacy.cpp:115
Wrapper class to help Hydrogen deal with the license information specified in a drumkit.
Definition License.h:48
QString getLicenseString() const
Definition License.h:146
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
int get_position() const
__position accessor
Definition Note.h:534
static Note * load_from(XMLNode *node, std::shared_ptr< InstrumentList > instruments, bool bSilent=false)
load a note from an XMLNode
Definition Note.cpp:500
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:499
void set_just_recorded(bool value)
__just_recorded setter
Definition Note.h:599
int get_length() const
__length accessor
Definition Note.h:559
Key
possible keys
Definition Note.h:105
Octave
possible octaves
Definition Note.h:109
bool match(std::shared_ptr< Instrument > instrument, Key key, Octave octave) const
return true if instrument, key and octave matches with internal
Definition Note.h:713
PatternList is a collection of patterns.
Definition PatternList.h:43
void add(Pattern *pattern, bool bAddVirtuals=false)
add a pattern to the list
Pattern * del(int idx)
remove the pattern at a given index, does not delete it
QString __name
the name of thepattern
Definition Pattern.h:259
bool save_file(const QString &drumkit_name, const QString &author, const License &license, const QString &pattern_path, bool overwrite=false) const
save a pattern into an xml file
Definition Pattern.cpp:147
const QString & get_info() const
get the category of the pattern
Definition Pattern.h:320
const QString & get_name() const
set the category of the pattern
Definition Pattern.h:310
const QString & get_category() const
set the length of the pattern
Definition Pattern.h:330
void set_to_old()
mark all notes as old
Definition Pattern.cpp:312
QString __info
a description of the pattern
Definition Pattern.h:261
void purge_instrument(std::shared_ptr< Instrument > instr, bool bRequiredLock=true)
delete the notes referencing the given instrument The function is thread safe (it locks the audio dat...
Definition Pattern.cpp:262
bool references(std::shared_ptr< Instrument > instr)
check if this pattern contains a note referencing the given instrument
Definition Pattern.cpp:250
virtual_patterns_t __virtual_patterns
a list of patterns directly referenced by this one
Definition Pattern.h:263
Pattern(const QString &name="Pattern", const QString &info="", const QString &category="not_categorized", int length=MAX_NOTES, int denominator=4)
constructor
Definition Pattern.cpp:38
notes_t __notes
a multimap (hash with possible multiple values for one key) of note
Definition Pattern.h:262
static Pattern * load_file(const QString &sPpatternPath, std::shared_ptr< InstrumentList > pInstruments, bool bSilent=false)
load a pattern from a file
Definition Pattern.cpp:66
static Pattern * load_from(XMLNode *node, std::shared_ptr< InstrumentList > instruments, bool bSilent=false)
load a pattern from an XMLNode
Definition Pattern.cpp:116
void flattened_virtual_patterns_compute()
compute virtual_pattern_transitive_closure_set based on virtual_pattern_transitive_closure_set virtua...
Definition Pattern.cpp:321
void remove_note(Note *note)
removes a given note from __notes, it's not deleted
Definition Pattern.cpp:239
int __length
Determines the accessible range or notes within the pattern.
Definition Pattern.h:257
notes_t::iterator notes_it_t
multimap note const iterator type
Definition Pattern.h:52
int get_length() const
set the denominator of the pattern
Definition Pattern.h:340
void clear(bool bRequiredLock=true)
Erase all notes.
Definition Pattern.cpp:289
std::set< Pattern * >::iterator begin()
allow iteration of all contained virtual patterns.
Definition Pattern.cpp:367
const notes_t * get_notes() const
get the virtual pattern set
Definition Pattern.h:355
~Pattern()
destructor
Definition Pattern.cpp:59
notes_t::const_iterator notes_cst_it_t
note set type;
Definition Pattern.h:54
void removeFlattenedVirtualPatterns(PatternList *pPatternList)
Add content of __flattened_virtual_patterns into pPatternList.
Definition Pattern.cpp:344
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
Definition Pattern.cpp:375
int get_denominator() const
get the note multimap
Definition Pattern.h:350
Note * find_note(int idx_a, int idx_b, std::shared_ptr< Instrument > instrument, bool strict=true) const
search for a note at a given index within __notes which correspond to the given arguments
Definition Pattern.cpp:212
virtual_patterns_t::const_iterator virtual_patterns_cst_it_t
Definition Pattern.h:60
void save_to(XMLNode *node, const std::shared_ptr< Instrument > instrumentOnly=nullptr) const
save the pattern within the given XMLNode
Definition Pattern.cpp:164
virtual_patterns_t __flattened_virtual_patterns
the complete list of virtual patterns
Definition Pattern.h:264
QString __category
the category of the pattern
Definition Pattern.h:260
bool isVirtual() const
Whether the pattern holds at least one virtual pattern.
Definition Pattern.cpp:363
int longestVirtualPatternLength() const
Definition Pattern.cpp:351
void insert_note(Note *note)
insert a new note within __notes
Definition Pattern.h:370
std::set< Pattern * >::iterator end()
Definition Pattern.cpp:371
int __denominator
the meter denominator of the pattern used in meter (eg 4/4)
Definition Pattern.h:258
void addFlattenedVirtualPatterns(PatternList *pPatternList)
Add content of __flattened_virtual_patterns into pPatternList.
Definition Pattern.cpp:337
XMLDoc is a subclass of QDomDocument with read and write methods.
Definition Xml.h:182
XMLNode set_root(const QString &node_name, const QString &xmlns=nullptr)
create the xml header and root node
Definition Xml.cpp:336
bool read(const QString &filepath, bool bSilent=false)
read the content of an xml file
Definition Xml.cpp:277
bool write(const QString &filepath)
write itself into a file
Definition Xml.cpp:311
XMLNode is a subclass of QDomNode with read and write values methods.
Definition Xml.h:39
int read_int(const QString &node, int default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads an integer stored into a child node
Definition Xml.cpp:151
QString read_string(const QString &node, const QString &default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a string stored into a child node
Definition Xml.cpp:76
XMLNode createNode(const QString &name)
create a new XMLNode that has to be appended into de XMLDoc
Definition Xml.cpp:44
void write_string(const QString &node, const QString &value)
write a string into a child node
Definition Xml.cpp:250
void write_int(const QString &node, const int value)
write an integer into a child node
Definition Xml.cpp:265