hydrogen 1.2.3
Legacy.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-2024 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 <memory>
24#include <QFile>
25#include <QByteArray>
26#include <QTextCodec>
27
28#include <core/Helpers/Legacy.h>
29
30#include "Version.h"
31#include <core/Helpers/Xml.h>
32#include <core/License.h>
33#include <core/Basics/Song.h>
34#include <core/Basics/Drumkit.h>
37#include <core/Basics/Pattern.h>
43#include <core/Basics/Sample.h>
44#include <core/Basics/Note.h>
45#include <core/Basics/Adsr.h>
46
47namespace H2Core {
48
49std::shared_ptr<InstrumentComponent> Legacy::loadInstrumentComponent( XMLNode* pNode, const QString& sDrumkitPath, const License& drumkitLicense, bool bSilent ) {
50 if ( ! bSilent ) {
51 WARNINGLOG( "Using back compatibility code to load instrument component" );
52 }
53
54 if ( pNode->firstChildElement( "filename" ).isNull() ) {
55 // not that old but no component yet.
56 auto pCompo = std::make_shared<InstrumentComponent>( 0 );
57
58 XMLNode layerNode = pNode->firstChildElement( "layer" );
59 int nLayer = 0;
60 while ( ! layerNode.isNull() ) {
61 if ( nLayer >= InstrumentComponent::getMaxLayers() ) {
62 ERRORLOG( QString( "Layer #%1 >= m_nMaxLayers (%2). This as well as all further layers will be omitted." )
63 .arg( nLayer )
65 break;
66 }
67
68 auto pLayer = InstrumentLayer::load_from( &layerNode, sDrumkitPath,
69 drumkitLicense, bSilent );
70 if ( pLayer != nullptr ) {
71 pCompo->set_layer( pLayer, nLayer );
72 nLayer++;
73 }
74 layerNode = layerNode.nextSiblingElement( "layer" );
75 }
76
77 if ( nLayer == 0 ) {
78 ERRORLOG( "Unable to load instrument component. Neither 'filename', 'instrumentComponent', nor 'layer' node found. Aborting." );
79 return nullptr;
80 }
81
82 return pCompo;
83 }
84 else {
85 // back compatibility code ( song version <= 0.9.0 )
86 QString sFilename = pNode->read_string( "filename", "", false, false, bSilent );
87
88 if ( ! Filesystem::file_exists( sFilename ) && ! sDrumkitPath.isEmpty() ) {
89 sFilename = sDrumkitPath + "/" + sFilename;
90 }
91
92 auto pSample = Sample::load( sFilename, drumkitLicense );
93 if ( pSample == nullptr ) {
94 // nel passaggio tra 0.8.2 e 0.9.0 il drumkit di default e' cambiato.
95 // Se fallisce provo a caricare il corrispettivo file in
96 // formato flac
97 if ( ! bSilent ) {
98 WARNINGLOG( "[readSong] Error loading sample: " +
99 sFilename + " not found. Trying to load a flac..." );
100 }
101 sFilename = sFilename.left( sFilename.length() - 4 );
102 sFilename += ".flac";
103 pSample = Sample::load( sFilename, drumkitLicense );
104 }
105 if ( pSample == nullptr ) {
106 ERRORLOG( "Error loading sample: " + sFilename + " not found" );
107 }
108
109 auto pCompo = std::make_shared<InstrumentComponent>( 0 );
110 auto pLayer = std::make_shared<InstrumentLayer>( pSample );
111 pCompo->set_layer( pLayer, 0 );
112 return pCompo;
113 }
114}
115
116Pattern* Legacy::load_drumkit_pattern( const QString& pattern_path, std::shared_ptr<InstrumentList> pInstrumentList ) {
117 Pattern* pPattern = nullptr;
118 if ( version_older_than( 0, 9, 8 ) ) {
119 WARNINGLOG( QString( "this code should not be used anymore, it belongs to 0.9.6" ) );
120 } else {
121 WARNINGLOG( QString( "loading pattern with legacy code" ) );
122 }
123 XMLDoc doc;
124 if( !doc.read( pattern_path ) ) {
125 return nullptr;
126 }
127 XMLNode root = doc.firstChildElement( "drumkit_pattern" );
128 if ( root.isNull() ) {
129 ERRORLOG( "drumkit_pattern node not found" );
130 return nullptr;
131 }
132 XMLNode pattern_node = root.firstChildElement( "pattern" );
133 if ( pattern_node.isNull() ) {
134 WARNINGLOG( "pattern node not found" );
135 return nullptr;
136 }
137
138 QString sName = pattern_node.read_string( "pattern_name", "", false, false );
139 if ( sName.isEmpty() ) {
140 sName = pattern_node.read_string( "pattern_name", "unknown", false, false );
141 }
142 QString sInfo = pattern_node.read_string( "info", "" );
143 QString sCategory = pattern_node.read_string( "category", "" );
144 int nSize = pattern_node.read_int( "size", -1, false, false );
145
146 //default nDenominator = 4 since old patterns have not <denominator> setting
147 pPattern = new Pattern( sName, sInfo, sCategory, nSize, 4 );
148
149 if ( pInstrumentList == nullptr ) {
150 ERRORLOG( "invalid instrument list provided" );
151 return pPattern;
152 }
153
154 XMLNode note_list_node = pattern_node.firstChildElement( "noteList" );
155
156 if ( ! note_list_node.isNull() ) {
157 // Less old version of the pattern format.
158 XMLNode note_node = note_list_node.firstChildElement( "note" );
159
160 while ( !note_node.isNull() ) {
161 Note* pNote = nullptr;
162 unsigned nPosition = note_node.read_int( "position", 0 );
163 float fLeadLag = note_node.read_float( "leadlag", 0.0 , false , false);
164 float fVelocity = note_node.read_float( "velocity", 0.8f );
165 float fPanL = note_node.read_float( "pan_L", 0.5 );
166 float fPanR = note_node.read_float( "pan_R", 0.5 );
167 float fPan = Sampler::getRatioPan( fPanL, fPanR ); // convert to single pan parameter
168
169 int nLength = note_node.read_int( "length", -1, true );
170 float nPitch = note_node.read_float( "pitch", 0.0, false, false );
171 float fProbability = note_node.read_float( "probability", 1.0 , false , false );
172 QString sKey = note_node.read_string( "key", "C0", false, false );
173 QString nNoteOff = note_node.read_string( "note_off", "false", false, false );
174 int instrId = note_node.read_int( "instrument", 0, true );
175
176 auto instrRef = pInstrumentList->find( instrId );
177 if ( !instrRef ) {
178 ERRORLOG( QString( "Instrument with ID: '%1' not found. Note skipped." ).arg( instrId ) );
179 note_node = note_node.nextSiblingElement( "note" );
180
181 continue;
182 }
183 //assert( instrRef );
184 bool noteoff = false;
185 if ( nNoteOff == "true" ) {
186 noteoff = true;
187 }
188
189 pNote = new Note( instrRef, nPosition, fVelocity, fPan, nLength, nPitch);
190 pNote->set_key_octave( sKey );
191 pNote->set_lead_lag(fLeadLag);
192 pNote->set_note_off( noteoff );
193 pNote->set_probability( fProbability );
194 pPattern->insert_note( pNote );
195
196 note_node = note_node.nextSiblingElement( "note" );
197 }
198 }
199 else {
200 // Back compatibility code for versions < 0.9.4
201 XMLNode sequenceListNode = pattern_node.firstChildElement( "sequenceList" );
202
203 int sequence_count = 0;
204 XMLNode sequenceNode = sequenceListNode.firstChildElement( "sequence" );
205 while ( ! sequenceNode.isNull() ) {
206 sequence_count++;
207
208 XMLNode noteListNode = sequenceNode.firstChildElement( "noteList" );
209 XMLNode noteNode = noteListNode.firstChildElement( "note" );
210 while ( !noteNode.isNull() ) {
211
212 int nInstrId = noteNode.read_int( "instrument", -1 );
213
214 auto pInstr = pInstrumentList->find( nInstrId );
215 if ( pInstr == nullptr ) {
216 ERRORLOG( QString( "Unable to retrieve instrument [%1]" )
217 .arg( nInstrId ) );
218 continue;
219 }
220
221 // convert to single pan parameter
222 float fPanL = noteNode.read_float( "pan_L", 0.5 );
223 float fPanR = noteNode.read_float( "pan_R", 0.5 );
224 float fPan = Sampler::getRatioPan( fPanL, fPanR );
225
226 Note* pNote = new Note( pInstr,
227 noteNode.read_int( "position", 0 ),
228 noteNode.read_float( "velocity", 0.8f ),
229 fPan,
230 noteNode.read_int( "length", -1, true ),
231 noteNode.read_float( "pitch", 0.0, false, false ) );
232 pNote->set_lead_lag( noteNode.read_float( "leadlag", 0.0, false, false ) );
233
234 pPattern->insert_note( pNote );
235
236 noteNode = noteNode.nextSiblingElement( "note" );
237 }
238 sequenceNode = sequenceNode.nextSiblingElement( "sequence" );
239 }
240 }
241
242 return pPattern;
243}
244
245Playlist* Legacy::load_playlist( Playlist* pPlaylist, const QString& pl_path )
246{
247 if ( version_older_than( 0, 9, 8 ) ) {
248 WARNINGLOG( QString( "this code should not be used anymore, it belongs to 0.9.6" ) );
249 } else {
250 WARNINGLOG( QString( "loading playlist with legacy code" ) );
251 }
252 XMLDoc doc;
253 if( !doc.read( pl_path ) ) {
254 return nullptr;
255 }
256 XMLNode root = doc.firstChildElement( "playlist" );
257 if ( root.isNull() ) {
258 ERRORLOG( "playlist node not found" );
259 return nullptr;
260 }
261 QFileInfo fileInfo = QFileInfo( pl_path );
262 QString filename = root.read_string( "Name", "", false, false );
263 if ( filename.isEmpty() ) {
264 ERRORLOG( "Playlist has no name, abort" );
265 return nullptr;
266 }
267
268 pPlaylist->setFilename( pl_path );
269
270 XMLNode songsNode = root.firstChildElement( "Songs" );
271 if ( !songsNode.isNull() ) {
272 XMLNode nextNode = songsNode.firstChildElement( "next" );
273 while ( !nextNode.isNull() ) {
274
275 QString songPath = nextNode.read_string( "song", "", false, false );
276 if ( !songPath.isEmpty() ) {
277 Playlist::Entry* entry = new Playlist::Entry();
278 QFileInfo songPathInfo( fileInfo.absoluteDir(), songPath );
279 entry->filePath = songPathInfo.absoluteFilePath();
280 entry->fileExists = songPathInfo.isReadable();
281 entry->scriptPath = nextNode.read_string( "script", "" );
282 entry->scriptEnabled = nextNode.read_bool( "enabled", false );
283 pPlaylist->add( entry );
284 }
285
286 nextNode = nextNode.nextSiblingElement( "next" );
287 }
288 } else {
289 WARNINGLOG( "Songs node not found" );
290 }
291 return pPlaylist;
292}
293
294std::vector<PatternList*>* Legacy::loadPatternGroupVector( XMLNode* pNode, PatternList* pPatternList, bool bSilent ) {;
295
296 std::vector<PatternList*>* pPatternGroupVector = new std::vector<PatternList*>;
297
298 if ( ! bSilent ) {
299 WARNINGLOG( "Using old pattern group vector code for back compatibility" );
300 }
301
302 XMLNode pPatternIDNode = pNode->firstChildElement( "patternID" );
303 while ( ! pPatternIDNode.isNull() ) {
304
305 PatternList* pPatternSequence = new PatternList();
306 QString sPatId = pPatternIDNode.firstChildElement().text();
307
308 Pattern* pPattern = nullptr;
309 for ( const auto& ppPat : *pPatternList ) {
310 if ( ppPat != nullptr ) {
311 if ( ppPat->get_name() == sPatId ) {
312 pPattern = ppPat;
313 break;
314 }
315 }
316 }
317
318 if ( pPattern == nullptr ) {
319 if ( ! bSilent ) {
320 WARNINGLOG( QString( "Pattern [%1] not found in patternList." )
321 .arg( sPatId ) );
322 }
323 delete pPatternSequence;
324 }
325 else {
326 pPatternSequence->add( pPattern );
327 pPatternGroupVector->push_back( pPatternSequence );
328 }
329
330 pPatternIDNode = pPatternIDNode.nextSiblingElement( "patternID" );
331 }
332
333 return pPatternGroupVector;
334}
335
336bool Legacy::checkTinyXMLCompatMode( QFile* pFile, bool bSilent ) {
337 if ( pFile == nullptr ) {
338 ERRORLOG( "Supplied file not valid" );
339 return false;
340 }
341
342 if ( ! pFile->seek( 0 ) ) {
343 ERRORLOG( QString( "Unable to move to the beginning of file [%1]. Compatibility check mmight fail." )
344 .arg( pFile->fileName() ) );
345 }
346
347 QString sFirstLine = pFile->readLine();
348 if ( ! sFirstLine.startsWith( "<?xml" ) ) {
349 WARNINGLOG( QString( "File [%1] is being read in TinyXML compatibility mode")
350 .arg( pFile->fileName() ) );
351 return true;
352 }
353
354 return false;
355
356}
357
358QByteArray Legacy::convertFromTinyXML( QFile* pFile, bool bSilent ) {
359 if ( pFile == nullptr ) {
360 ERRORLOG( "Supplied file not valid" );
361 return QByteArray();
362 }
363
364 if ( ! pFile->seek( 0 ) ) {
365 ERRORLOG( QString( "Unable to move to the beginning of file [%1]. Converting mmight fail." )
366 .arg( pFile->fileName() ) );
367 }
368
369 QString sEncoding = QTextCodec::codecForLocale()->name();
370 if ( sEncoding == "System" ) {
371 sEncoding = "UTF-8";
372 }
373 QByteArray line;
374 QByteArray buf = QString("<?xml version='1.0' encoding='%1' ?>\n")
375 .arg( sEncoding )
376 .toLocal8Bit();
377
378 while ( ! pFile->atEnd() ) {
379 line = pFile->readLine();
381 buf += line;
382 }
383
384 return std::move( buf );
385}
386
387void Legacy::convertStringFromTinyXML( QByteArray* pString ) {
388
389 /* When TinyXML encountered a non-ASCII character, it would
390 * simply write the character as "&#xx;" -- where "xx" is
391 * the hex character code. However, this doesn't respect
392 * any encodings (e.g. UTF-8, UTF-16). In XML, &#xx; literally
393 * means "the Unicode character # xx." However, in a UTF-8
394 * sequence, this could be an escape character that tells
395 * whether we have a 2, 3, or 4-byte UTF-8 sequence.
396 *
397 * For example, the UTF-8 sequence 0xD184 was being written
398 * by TinyXML as "&#xD1;&#x84;". However, this is the UTF-8
399 * sequence for the cyrillic small letter EF (which looks
400 * kind of like a thorn or a greek phi). This letter, in
401 * XML, should be saved as &#x00000444;, or even literally
402 * (no escaping). As a consequence, when &#xD1; is read
403 * by an XML parser, it will be interpreted as capital N
404 * with a tilde (~). Then &#x84; will be interpreted as
405 * an unknown or control character.
406 *
407 * So, when we know that TinyXML wrote the file, we can
408 * simply exchange these hex sequences to literal bytes.
409 */
410 int nPos = 0;
411
412 nPos = pString->indexOf( "&#x" );
413 while ( nPos != -1 ) {
414 if ( isxdigit( pString->at( nPos + 3 ) ) &&
415 isxdigit( pString->at( nPos + 4 ) ) &&
416 pString->at( nPos + 5 ) == ';' ) {
417
418 char w1 = pString->at( nPos + 3 );
419 char w2 = pString->at( nPos + 4 );
420
421 w1 = tolower( w1 ) - 0x30; // '0' = 0x30
422 if ( w1 > 9 ) {
423 w1 -= 0x27; // '9' = 0x39, 'a' = 0x61
424 }
425 w1 = ( w1 & 0xF );
426
427 w2 = tolower( w2 ) - 0x30; // '0' = 0x30
428 if ( w2 > 9 ) {
429 w2 -= 0x27; // '9' = 0x39, 'a' = 0x61
430 }
431 w2 = ( w2 & 0xF );
432
433 char ch = ( w1 << 4 ) | w2;
434 (*pString)[nPos] = ch;
435 ++nPos;
436 pString->remove( nPos, 5 );
437 }
438 nPos = pString->indexOf( "&#x" );
439 }
440}
441};
442
443/* vim: set softtabstop=4 noexpandtab: */
#define WARNINGLOG(x)
Definition Object.h:238
#define ERRORLOG(x)
Definition Object.h:239
static bool file_exists(const QString &path, bool silent=false)
returns true if the given path is an existing regular file
static std::shared_ptr< InstrumentLayer > load_from(XMLNode *pNode, const QString &sDrumkitPath, const License &drumkitLicense=License(), bool bSilent=false)
load an instrument layer from an XMLNode
static void convertStringFromTinyXML(QByteArray *pString)
Convert (in-place) an XML escape sequence into a literal byte, rather than the character it actually ...
Definition Legacy.cpp:387
static Pattern * load_drumkit_pattern(const QString &pattern_path, std::shared_ptr< InstrumentList > instrList)
load pattern from a file
Definition Legacy.cpp:116
static bool checkTinyXMLCompatMode(QFile *pFile, bool bSilent=false)
Definition Legacy.cpp:336
static std::vector< PatternList * > * loadPatternGroupVector(XMLNode *pNode, PatternList *pPatternList, bool bSilent=false)
Definition Legacy.cpp:294
static QByteArray convertFromTinyXML(QFile *pFile, bool bSilent=false)
Definition Legacy.cpp:358
static Playlist * load_playlist(Playlist *pl, const QString &pl_path)
load playlist from a file
Definition Legacy.cpp:245
static std::shared_ptr< InstrumentComponent > loadInstrumentComponent(XMLNode *pNode, const QString &sDrumkitPath, const License &drumkitLicense, bool bSilent=false)
Backward compatibility code to load an #InstrumentComponent from an #Instrument which itself did not ...
Definition Legacy.cpp:49
Wrapper class to help Hydrogen deal with the license information specified in a drumkit.
Definition License.h:48
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:102
void set_lead_lag(float value)
__lead_lag setter
Definition Note.cpp:148
void set_key_octave(const QString &str)
parse str and set __key and __octave
Definition Note.cpp:202
void set_probability(float value)
Definition Note.h:615
void set_note_off(bool value)
__note_off setter
Definition Note.h:575
PatternList is a collection of patterns.
Definition PatternList.h:43
void add(Pattern *pattern, bool bAddVirtuals=false)
add a pattern to the list
Pattern class is a Note container.
Definition Pattern.h:46
void insert_note(Note *note)
insert a new note within __notes
Definition Pattern.h:370
Drumkit info.
Definition Playlist.h:37
void add(Entry *entry)
Definition Playlist.h:134
void setFilename(const QString &filename)
Definition Playlist.h:164
static std::shared_ptr< Sample > load(const QString &filepath, const License &license=License())
Definition Sample.cpp:136
static float getRatioPan(float fPan_L, float fPan_R)
This function is used to load old version files (v<=1.1).
Definition Sampler.cpp:257
XMLDoc is a subclass of QDomDocument with read and write methods.
Definition Xml.h:182
bool read(const QString &filepath, const QString &schemapath=nullptr, bool bSilent=false)
read the content of an xml file
Definition Xml.cpp:296
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:170
bool read_bool(const QString &node, bool default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a boolean stored into a child node
Definition Xml.cpp:184
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:95
float read_float(const QString &node, float default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a float stored into a child node
Definition Xml.cpp:139
bool version_older_than(int major, int minor, int patch)
return true of the current version is older than the given values
Definition Version.cpp:34