hydrogen 1.2.6
InstrumentLayer.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
25#include <core/Basics/Sample.h>
26
28#include <core/Helpers/Xml.h>
29#include <core/License.h>
30#include <core/Hydrogen.h>
31#include <core/NsmClient.h>
33
34namespace H2Core
35{
36
37InstrumentLayer::InstrumentLayer( std::shared_ptr<Sample> sample ) :
38 __start_velocity( 0.0 ),
39 __end_velocity( 1.0 ),
40 __pitch( 0.0 ),
41 __gain( 1.0 ),
42 __sample( sample )
43{
44}
45
46InstrumentLayer::InstrumentLayer( std::shared_ptr<InstrumentLayer> other ) : Object( *other ),
49 __pitch( other->get_pitch() ),
50 __gain( other->get_gain() ),
51 __sample( other->get_sample() )
52{
53}
54
55InstrumentLayer::InstrumentLayer( std::shared_ptr<InstrumentLayer> other, std::shared_ptr<Sample> sample ) : Object( *other ),
58 __pitch( other->get_pitch() ),
59 __gain( other->get_gain() ),
60 __sample( sample )
61{
62}
63
67
68void InstrumentLayer::set_sample( std::shared_ptr<Sample> sample )
69{
70 __sample = sample;
71}
72
73void InstrumentLayer::set_pitch( float fValue )
74{
75 if ( fValue < Instrument::fPitchMin || fValue > Instrument::fPitchMax ) {
76 WARNINGLOG( QString( "Provided pitch out of bound [%1;%2]. Rounding to nearest allowed value." )
78 }
80}
81
83{
84 if ( __sample != nullptr ) {
85 __sample->load( fBpm );
86 }
87}
88
90{
91 if ( __sample != nullptr ) {
92 __sample->unload();
93 }
94}
95
96std::shared_ptr<InstrumentLayer> InstrumentLayer::load_from( XMLNode* pNode, const QString& sDrumkitPath, const License& drumkitLicense, bool bSilent )
97{
98 auto pHydrogen = Hydrogen::get_instance();
99
100 const QString sFileName = pNode->read_string(
101 "filename", "", false, false, bSilent );
102 QString sFilePath = sFileName;
103
104 // In case just the filename is provided, like "sample.wav", the
105 // corresponding sample will be searched in the folder of the corresponding
106 // drumkit.
107 if ( ! Filesystem::file_exists( sFileName, true ) && ! sDrumkitPath.isEmpty() &&
108 ! sFileName.startsWith( "/" ) ) {
109
110#ifdef H2CORE_HAVE_OSC
111 if ( pHydrogen->isUnderSessionManagement() ) {
112 // If we use the NSM support and the sample files to save
113 // are corresponding to the drumkit linked/located in the
114 // session folder, we have to ensure the relative paths
115 // are loaded. This is vital in order to support
116 // renaming, duplicating, and porting sessions.
117
118 // QFileInfo::isRelative() can not be used in here as
119 // samples within drumkits within the user or system
120 // drumkit folder are stored relatively as well (by saving
121 // just the filename).
122 if ( sFileName.left( 2 ) == "./" ||
123 sFileName.left( 2 ) == ".\\" ) {
124 // Removing the leading "." of the relative path in
125 // sFileName while still using the associated folder
126 // separator.
128 sFileName.right( sFileName.size() - 1 );
129 }
130 else {
131 sFilePath = sDrumkitPath + "/" + sFileName;
132 }
133 }
134 else {
135 sFilePath = sDrumkitPath + "/" + sFileName;
136 }
137#else
138 sFilePath = sDrumkitPath + "/" + sFileName;
139#endif
140 }
141
142 // If the sample still could not be found, this could be e.g. due to an
143 // absolute path referencing a sample imported from a session kit - one,
144 // which was loaded by the user manually and does not reside in either user
145 // or system drumkit folder - or due to a bug like #2174. We give it another
146 // try by checking whether the /path/to/<drumkit>/<sample> could refer to
147 // the exact same <drumkit>/<sample> in one of our drumkit folders.
148 if ( ! Filesystem::file_exists( sFilePath, true ) &&
149 ( sFileName.contains( "/" ) || sFileName.contains( "\\" ) ) ) {
150 // We need to ensure we work on a single set of separators without any
151 // duplication. This is especially important as songs created on Windows
152 // could be loaded on Linux/macOS and vice versa.
153 const QString sFileNameCleaned = QString( sFileName )
154 .replace( "\\", "/" ).replace( "//", "/" );
155
156 const auto pathSegments = sFileNameCleaned.split( "/" );
157 if ( pathSegments.size() > 2 ) {
158 const auto sDrumkitSampleSegment = QString( "%1/%2" )
159 .arg( pathSegments[ pathSegments.size() - 2 ] )
160 .arg( pathSegments[ pathSegments.size() - 1 ] );
161
162 const auto drumkitFolders = QStringList() <<
164 for ( const auto& ssFolder : drumkitFolders ) {
165 const auto sNewPath = QString( "%1/%2" )
166 .arg( ssFolder ).arg( sDrumkitSampleSegment );
167 if ( Filesystem::file_exists( sNewPath, true ) ) {
168 WARNINGLOG( QString( "File [%1] does not exist. Loading similar file [%2] instead." )
169 .arg( sFileName ).arg( sNewPath ) );
170 sFilePath = sNewPath;
171 break;
172 }
173 }
174 }
175 }
176
177 std::shared_ptr<Sample> pSample = nullptr;
178 if ( Filesystem::file_exists( sFilePath, true ) ) {
179 pSample = std::make_shared<Sample>( sFilePath, drumkitLicense );
180
181 // If 'ismodified' is not present, InstrumentLayer was stored as
182 // part of a drumkit. All the additional Sample info, like Loops,
183 // envelopes etc., were not written to disk and we won't load the
184 // sample.
185 bool bIsModified = pNode->read_bool( "ismodified", false, true, false, true );
186 pSample->set_is_modified( bIsModified );
187
188 if ( bIsModified ) {
189
190 Sample::Loops loops;
191 loops.mode = Sample::parse_loop_mode( pNode->read_string( "smode", "forward", false, false, bSilent ) );
192 loops.start_frame = pNode->read_int( "startframe", 0, false, false, bSilent );
193 loops.loop_frame = pNode->read_int( "loopframe", 0, false, false, bSilent );
194 loops.count = pNode->read_int( "loops", 0, false, false, bSilent );
195 loops.end_frame = pNode->read_int( "endframe", 0, false, false, bSilent );
196 pSample->set_loops( loops );
197
198 Sample::Rubberband rubberband;
199 rubberband.use = pNode->read_int( "userubber", 0, false, false, bSilent );
200 rubberband.divider = pNode->read_float( "rubberdivider", 0.0, false, false, bSilent );
201 rubberband.c_settings = pNode->read_int( "rubberCsettings", 1, false, false, bSilent );
202 rubberband.pitch = pNode->read_float( "rubberPitch", 0.0, false, false, bSilent );
203
204 // Check whether the rubberband executable is present.
206 m_rubberBandCLIexecutable ) ) {
207 rubberband.use = false;
208 }
209 pSample->set_rubberband( rubberband );
210
211 // FIXME, kill EnvelopePoint, create Envelope class
212 EnvelopePoint pt;
213
214 Sample::VelocityEnvelope velocityEnvelope;
215 XMLNode volumeNode = pNode->firstChildElement( "volume" );
216 while ( ! volumeNode.isNull() ) {
217 pt.frame = volumeNode.read_int( "volume-position", 0, false, false, bSilent );
218 pt.value = volumeNode.read_int( "volume-value", 0, false, false , bSilent);
219 velocityEnvelope.push_back( pt );
220 volumeNode = volumeNode.nextSiblingElement( "volume" );
221 }
222 pSample->set_velocity_envelope( velocityEnvelope );
223
224 Sample::VelocityEnvelope panEnvelope;
225 XMLNode panNode = pNode->firstChildElement( "pan" );
226 while ( ! panNode.isNull() ) {
227 pt.frame = panNode.read_int( "pan-position", 0, false, false, bSilent );
228 pt.value = panNode.read_int( "pan-value", 0, false, false, bSilent );
229 panEnvelope.push_back( pt );
230 panNode = panNode.nextSiblingElement( "pan" );
231 }
232 pSample->set_pan_envelope( panEnvelope );
233 }
234 }
235 else {
236 WARNINGLOG( QString( "Sample file [%1] does not exist at [%2]" )
237 .arg( sFileName ).arg( sFilePath ) );
238 }
239
240 auto pLayer = std::make_shared<InstrumentLayer>( pSample );
241 pLayer->set_start_velocity( pNode->read_float( "min", 0.0,
242 true, true, bSilent ) );
243 pLayer->set_end_velocity( pNode->read_float( "max", 1.0,
244 true, true, bSilent ) );
245 pLayer->set_gain( pNode->read_float( "gain", 1.0,
246 true, false, bSilent ) );
247 pLayer->set_pitch( pNode->read_float( "pitch", 0.0,
248 true, false, bSilent ) );
249 return pLayer;
250}
251
252void InstrumentLayer::save_to( XMLNode* node, bool bFull )
253{
254 auto pHydrogen = Hydrogen::get_instance();
255 auto pSample = get_sample();
256 if ( pSample == nullptr ) {
257 ERRORLOG( "No sample associated with layer. Skipping it" );
258 return;
259 }
260
261 XMLNode layer_node = node->createNode( "layer" );
262
263 QString sFileName;
264 if ( bFull ) {
265
266 if ( pHydrogen->isUnderSessionManagement() ) {
267 // If we use the NSM support and the sample files to save
268 // are corresponding to the drumkit linked/located in the
269 // session folder, we have to ensure the relative paths
270 // are written out. This is vital in order to support
271 // renaming, duplicating, and porting sessions.
272 if ( pSample->get_raw_filepath().startsWith( '.' ) ) {
273 sFileName = pSample->get_raw_filepath();
274 }
275 else {
276 sFileName = Filesystem::prepare_sample_path( pSample->get_filepath() );
277 }
278 }
279 else {
280 sFileName = Filesystem::prepare_sample_path( pSample->get_filepath() );
281 }
282 }
283 else {
284 sFileName = pSample->get_filename();
285 }
286
287 layer_node.write_string( "filename", sFileName );
288 layer_node.write_float( "min", __start_velocity );
289 layer_node.write_float( "max", __end_velocity );
290 layer_node.write_float( "gain", __gain );
291 layer_node.write_float( "pitch", __pitch );
292
293 if ( bFull ) {
294 layer_node.write_bool( "ismodified", pSample->get_is_modified() );
295 layer_node.write_string( "smode", pSample->get_loop_mode_string() );
296
297 Sample::Loops loops = pSample->get_loops();
298 layer_node.write_int( "startframe", loops.start_frame );
299 layer_node.write_int( "loopframe", loops.loop_frame );
300 layer_node.write_int( "loops", loops.count );
301 layer_node.write_int( "endframe", loops.end_frame );
302
303 Sample::Rubberband rubberband = pSample->get_rubberband();
304 layer_node.write_int( "userubber", static_cast<int>(rubberband.use) );
305 layer_node.write_float( "rubberdivider", rubberband.divider );
306 layer_node.write_int( "rubberCsettings", rubberband.c_settings );
307 layer_node.write_float( "rubberPitch", rubberband.pitch );
308
309 for ( const auto& velocity : *pSample->get_velocity_envelope() ) {
310 XMLNode volumeNode = layer_node.createNode( "volume" );
311 volumeNode.write_int( "volume-position", velocity.frame );
312 volumeNode.write_int( "volume-value", velocity.value );
313 }
314
315 for ( const auto& pan : *pSample->get_pan_envelope() ) {
316 XMLNode panNode = layer_node.createNode( "pan" );
317 panNode.write_int( "pan-position", pan.frame );
318 panNode.write_int( "pan-value", pan.value );
319 }
320 }
321}
322
323QString InstrumentLayer::toQString( const QString& sPrefix, bool bShort ) const {
324 QString s = Base::sPrintIndention;
325 QString sOutput;
326 if ( ! bShort ) {
327 sOutput = QString( "%1[InstrumentLayer]\n" ).arg( sPrefix )
328 .append( QString( "%1%2gain: %3\n" ).arg( sPrefix ).arg( s ).arg( __gain ) )
329 .append( QString( "%1%2pitch: %3\n" ).arg( sPrefix ).arg( s ).arg( __pitch ) )
330 .append( QString( "%1%2start_velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __start_velocity ) )
331 .append( QString( "%1%2end_velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __end_velocity ) );
332 if ( __sample != nullptr ) {
333 sOutput.append( QString( "%1" )
334 .arg( __sample->toQString( sPrefix + s, bShort ) ) );
335 } else {
336 sOutput.append( QString( "%1%2sample: nullptr\n" ).arg( sPrefix ).arg( s ) );
337 }
338 }
339 else {
340 sOutput = QString( "[InstrumentLayer]" )
341 .append( QString( " gain: %1" ).arg( __gain ) )
342 .append( QString( ", pitch: %1" ).arg( __pitch ) )
343 .append( QString( ", start_velocity: %1" ).arg( __start_velocity ) )
344 .append( QString( ", end_velocity: %1" ).arg( __end_velocity ) );
345 if ( __sample != nullptr ) {
346 sOutput.append( QString( ", sample: %1\n" ).arg( __sample->get_filepath() ) );
347 } else {
348 sOutput.append( QString( ", sample: nullptr\n" ) );
349 }
350 }
351
352 return sOutput;
353}
354
355};
356
357/* vim: set softtabstop=4 noexpandtab: */
#define WARNINGLOG(x)
Definition Object.h:241
#define ERRORLOG(x)
Definition Object.h:242
static QString sPrintIndention
String used to format the debugging string output of some core classes.
Definition Object.h:127
A container for a sample, being able to apply modifications on it.
Definition Sample.h:43
int frame
frame index
Definition Sample.h:46
static QString usr_drumkits_dir()
returns user drumkits path
static QString sys_drumkits_dir()
returns system drumkits path
static QString prepare_sample_path(const QString &sFilePath)
Returns the basename if the given path is under an existing user or system drumkit path,...
static bool file_exists(const QString &path, bool silent=false)
returns true if the given path is an existing regular file
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
float get_gain() const
get the gain of the layer
void set_pitch(float pitch)
set the pitch of the layer
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
float __start_velocity
the start velocity of the sample, 0.0 by default
float get_pitch() const
get the pitch of the layer
float __gain
ratio between the input sample and the output signal, 1.0 by default
float __pitch
the frequency of the sample, 0.0 by default which means output pitch is the same as input pitch
std::shared_ptr< Sample > __sample
the underlaying sample
void save_to(XMLNode *node, bool bFull=false)
save the instrument layer within the given XMLNode
InstrumentLayer(std::shared_ptr< Sample > sample)
constructor
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
std::shared_ptr< Sample > get_sample() const
get the sample of the layer
float get_start_velocity() const
get the start velocity of the layer
void load_sample(float fBpm=120)
Calls the H2Core::Sample::load() member function of __sample.
float __end_velocity
the end velocity of the sample, 1.0 by default
void set_sample(std::shared_ptr< Sample > sample)
set the sample of the layer
float get_end_velocity() const
get the end velocity of the layer
static constexpr float fPitchMin
Minimum support pitch value.
Definition Instrument.h:322
static constexpr float fPitchMax
Maximum support pitch value.
Definition Instrument.h:320
Wrapper class to help Hydrogen deal with the license information specified in a drumkit.
Definition License.h:48
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
set of loop configuration flags
Definition Sample.h:78
int end_frame
the frame index where to end the new sample to
Definition Sample.h:88
int start_frame
the frame index where to start the new sample from
Definition Sample.h:86
LoopMode mode
one of the possible loop modes
Definition Sample.h:90
int count
the counts of loops to apply
Definition Sample.h:89
int loop_frame
the frame index where to start the loop from
Definition Sample.h:87
set of rubberband configuration flags
Definition Sample.h:110
float pitch
desired pitch
Definition Sample.h:114
int c_settings
TODO should be crispness, see rubberband -h.
Definition Sample.h:115
float divider
TODO should be ratio : desired time ratio.
Definition Sample.h:113
bool use
is rubberband enabled
Definition Sample.h:112
static Loops::LoopMode parse_loop_mode(const QString &string)
parse the given string and rturn the corresponding loop_mode
Definition Sample.cpp:683
std::vector< EnvelopePoint > VelocityEnvelope
define the type used to store velocity envelope points
Definition Sample.h:75
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
void write_float(const QString &node, const float value)
write a float into a child node
Definition Xml.cpp:261
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_bool(const QString &node, const bool value)
write a boolean into a child node
Definition Xml.cpp:269
void write_int(const QString &node, const int value)
write an integer into a child node
Definition Xml.cpp:265
static NsmClient * get_instance()
Definition NsmClient.h:84
QString getSessionFolderPath() const
Definition NsmClient.h:335