hydrogen 1.2.5
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 QString sFilename = pNode->read_string( "filename", "", false, false, bSilent );
101 QString sAbsoluteFilename = sFilename;
102
103 if ( ! Filesystem::file_exists( sFilename, true ) && ! sDrumkitPath.isEmpty() &&
104 ! sFilename.startsWith( "/" ) ) {
105
106#ifdef H2CORE_HAVE_OSC
107 if ( pHydrogen->isUnderSessionManagement() ) {
108 // If we use the NSM support and the sample files to save
109 // are corresponding to the drumkit linked/located in the
110 // session folder, we have to ensure the relative paths
111 // are loaded. This is vital in order to support
112 // renaming, duplicating, and porting sessions.
113
114 // QFileInfo::isRelative() can not be used in here as
115 // samples within drumkits within the user or system
116 // drumkit folder are stored relatively as well (by saving
117 // just the filename).
118 if ( sFilename.left( 2 ) == "./" ||
119 sFilename.left( 2 ) == ".\\" ) {
120 // Removing the leading "." of the relative path in
121 // sFilename while still using the associated folder
122 // separator.
123 sAbsoluteFilename = NsmClient::get_instance()->getSessionFolderPath() +
124 sFilename.right( sFilename.size() - 1 );
125 }
126 else {
127 sFilename = sDrumkitPath + "/" + sFilename;
128 sAbsoluteFilename = sFilename;
129 }
130 }
131 else {
132 sFilename = sDrumkitPath + "/" + sFilename;
133 sAbsoluteFilename = sFilename;
134 }
135#else
136 sFilename = sDrumkitPath + "/" + sFilename;
137 sAbsoluteFilename = sFilename;
138#endif
139 }
140
141 std::shared_ptr<Sample> pSample = nullptr;
142 if ( Filesystem::file_exists( sAbsoluteFilename, true ) ) {
143 pSample = std::make_shared<Sample>( sFilename, drumkitLicense );
144
145 // If 'ismodified' is not present, InstrumentLayer was stored as
146 // part of a drumkit. All the additional Sample info, like Loops,
147 // envelopes etc., were not written to disk and we won't load the
148 // sample.
149 bool bIsModified = pNode->read_bool( "ismodified", false, true, false, true );
150 pSample->set_is_modified( bIsModified );
151
152 if ( bIsModified ) {
153
154 Sample::Loops loops;
155 loops.mode = Sample::parse_loop_mode( pNode->read_string( "smode", "forward", false, false, bSilent ) );
156 loops.start_frame = pNode->read_int( "startframe", 0, false, false, bSilent );
157 loops.loop_frame = pNode->read_int( "loopframe", 0, false, false, bSilent );
158 loops.count = pNode->read_int( "loops", 0, false, false, bSilent );
159 loops.end_frame = pNode->read_int( "endframe", 0, false, false, bSilent );
160 pSample->set_loops( loops );
161
162 Sample::Rubberband rubberband;
163 rubberband.use = pNode->read_int( "userubber", 0, false, false, bSilent );
164 rubberband.divider = pNode->read_float( "rubberdivider", 0.0, false, false, bSilent );
165 rubberband.c_settings = pNode->read_int( "rubberCsettings", 1, false, false, bSilent );
166 rubberband.pitch = pNode->read_float( "rubberPitch", 0.0, false, false, bSilent );
167
168 // Check whether the rubberband executable is present.
170 m_rubberBandCLIexecutable ) ) {
171 rubberband.use = false;
172 }
173 pSample->set_rubberband( rubberband );
174
175 // FIXME, kill EnvelopePoint, create Envelope class
176 EnvelopePoint pt;
177
178 Sample::VelocityEnvelope velocityEnvelope;
179 XMLNode volumeNode = pNode->firstChildElement( "volume" );
180 while ( ! volumeNode.isNull() ) {
181 pt.frame = volumeNode.read_int( "volume-position", 0, false, false, bSilent );
182 pt.value = volumeNode.read_int( "volume-value", 0, false, false , bSilent);
183 velocityEnvelope.push_back( pt );
184 volumeNode = volumeNode.nextSiblingElement( "volume" );
185 }
186 pSample->set_velocity_envelope( velocityEnvelope );
187
188 Sample::VelocityEnvelope panEnvelope;
189 XMLNode panNode = pNode->firstChildElement( "pan" );
190 while ( ! panNode.isNull() ) {
191 pt.frame = panNode.read_int( "pan-position", 0, false, false, bSilent );
192 pt.value = panNode.read_int( "pan-value", 0, false, false, bSilent );
193 panEnvelope.push_back( pt );
194 panNode = panNode.nextSiblingElement( "pan" );
195 }
196 pSample->set_pan_envelope( panEnvelope );
197 }
198 }
199 else {
200 WARNINGLOG( QString( "Sample file [%1] does not exist" )
201 .arg( sAbsoluteFilename ) );
202 }
203
204 auto pLayer = std::make_shared<InstrumentLayer>( pSample );
205 pLayer->set_start_velocity( pNode->read_float( "min", 0.0,
206 true, true, bSilent ) );
207 pLayer->set_end_velocity( pNode->read_float( "max", 1.0,
208 true, true, bSilent ) );
209 pLayer->set_gain( pNode->read_float( "gain", 1.0,
210 true, false, bSilent ) );
211 pLayer->set_pitch( pNode->read_float( "pitch", 0.0,
212 true, false, bSilent ) );
213 return pLayer;
214}
215
216void InstrumentLayer::save_to( XMLNode* node, bool bFull )
217{
218 auto pHydrogen = Hydrogen::get_instance();
219 auto pSample = get_sample();
220 if ( pSample == nullptr ) {
221 ERRORLOG( "No sample associated with layer. Skipping it" );
222 return;
223 }
224
225 XMLNode layer_node = node->createNode( "layer" );
226
227 QString sFilename;
228 if ( bFull ) {
229
230 if ( pHydrogen->isUnderSessionManagement() ) {
231 // If we use the NSM support and the sample files to save
232 // are corresponding to the drumkit linked/located in the
233 // session folder, we have to ensure the relative paths
234 // are written out. This is vital in order to support
235 // renaming, duplicating, and porting sessions.
236 if ( pSample->get_raw_filepath().startsWith( '.' ) ) {
237 sFilename = pSample->get_raw_filepath();
238 }
239 else {
240 sFilename = Filesystem::prepare_sample_path( pSample->get_filepath() );
241 }
242 }
243 else {
244 sFilename = Filesystem::prepare_sample_path( pSample->get_filepath() );
245 }
246 }
247 else {
248 sFilename = pSample->get_filename();
249 }
250
251 layer_node.write_string( "filename", sFilename );
252 layer_node.write_float( "min", __start_velocity );
253 layer_node.write_float( "max", __end_velocity );
254 layer_node.write_float( "gain", __gain );
255 layer_node.write_float( "pitch", __pitch );
256
257 if ( bFull ) {
258 layer_node.write_bool( "ismodified", pSample->get_is_modified() );
259 layer_node.write_string( "smode", pSample->get_loop_mode_string() );
260
261 Sample::Loops loops = pSample->get_loops();
262 layer_node.write_int( "startframe", loops.start_frame );
263 layer_node.write_int( "loopframe", loops.loop_frame );
264 layer_node.write_int( "loops", loops.count );
265 layer_node.write_int( "endframe", loops.end_frame );
266
267 Sample::Rubberband rubberband = pSample->get_rubberband();
268 layer_node.write_int( "userubber", static_cast<int>(rubberband.use) );
269 layer_node.write_float( "rubberdivider", rubberband.divider );
270 layer_node.write_int( "rubberCsettings", rubberband.c_settings );
271 layer_node.write_float( "rubberPitch", rubberband.pitch );
272
273 for ( const auto& velocity : *pSample->get_velocity_envelope() ) {
274 XMLNode volumeNode = layer_node.createNode( "volume" );
275 volumeNode.write_int( "volume-position", velocity.frame );
276 volumeNode.write_int( "volume-value", velocity.value );
277 }
278
279 for ( const auto& pan : *pSample->get_pan_envelope() ) {
280 XMLNode panNode = layer_node.createNode( "pan" );
281 panNode.write_int( "pan-position", pan.frame );
282 panNode.write_int( "pan-value", pan.value );
283 }
284 }
285}
286
287QString InstrumentLayer::toQString( const QString& sPrefix, bool bShort ) const {
288 QString s = Base::sPrintIndention;
289 QString sOutput;
290 if ( ! bShort ) {
291 sOutput = QString( "%1[InstrumentLayer]\n" ).arg( sPrefix )
292 .append( QString( "%1%2gain: %3\n" ).arg( sPrefix ).arg( s ).arg( __gain ) )
293 .append( QString( "%1%2pitch: %3\n" ).arg( sPrefix ).arg( s ).arg( __pitch ) )
294 .append( QString( "%1%2start_velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __start_velocity ) )
295 .append( QString( "%1%2end_velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __end_velocity ) );
296 if ( __sample != nullptr ) {
297 sOutput.append( QString( "%1" )
298 .arg( __sample->toQString( sPrefix + s, bShort ) ) );
299 } else {
300 sOutput.append( QString( "%1%2sample: nullptr\n" ).arg( sPrefix ).arg( s ) );
301 }
302 }
303 else {
304 sOutput = QString( "[InstrumentLayer]" )
305 .append( QString( " gain: %1" ).arg( __gain ) )
306 .append( QString( ", pitch: %1" ).arg( __pitch ) )
307 .append( QString( ", start_velocity: %1" ).arg( __start_velocity ) )
308 .append( QString( ", end_velocity: %1" ).arg( __end_velocity ) );
309 if ( __sample != nullptr ) {
310 sOutput.append( QString( ", sample: %1\n" ).arg( __sample->get_filepath() ) );
311 } else {
312 sOutput.append( QString( ", sample: nullptr\n" ) );
313 }
314 }
315
316 return sOutput;
317}
318
319};
320
321/* 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 prepare_sample_path(const QString &fname)
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