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