hydrogen 1.2.3
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-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
24
26#include <core/Helpers/Xml.h>
27#include <core/Basics/Sample.h>
28#include <core/License.h>
29#include <core/Hydrogen.h>
30#include <core/NsmClient.h>
32
33namespace H2Core
34{
35
36InstrumentLayer::InstrumentLayer( std::shared_ptr<Sample> sample ) :
37 __start_velocity( 0.0 ),
38 __end_velocity( 1.0 ),
39 __pitch( 0.0 ),
40 __gain( 1.0 ),
41 __sample( sample )
42{
43}
44
45InstrumentLayer::InstrumentLayer( std::shared_ptr<InstrumentLayer> other ) : Object( *other ),
46 __start_velocity( other->get_start_velocity() ),
47 __end_velocity( other->get_end_velocity() ),
48 __pitch( other->get_pitch() ),
49 __gain( other->get_gain() ),
50 __sample( other->get_sample() )
51{
52}
53
54InstrumentLayer::InstrumentLayer( std::shared_ptr<InstrumentLayer> other, std::shared_ptr<Sample> sample ) : Object( *other ),
55 __start_velocity( other->get_start_velocity() ),
56 __end_velocity( other->get_end_velocity() ),
57 __pitch( other->get_pitch() ),
58 __gain( other->get_gain() ),
59 __sample( sample )
60{
61}
62
66
67void InstrumentLayer::set_sample( std::shared_ptr<Sample> sample )
68{
69 __sample = sample;
70}
71
73{
74 if ( __sample != nullptr ) {
75 __sample->load( fBpm );
76 }
77}
78
80{
81 if ( __sample != nullptr ) {
82 __sample->unload();
83 }
84}
85
86std::shared_ptr<InstrumentLayer> InstrumentLayer::load_from( XMLNode* pNode, const QString& sDrumkitPath, const License& drumkitLicense, bool bSilent )
87{
88 auto pHydrogen = Hydrogen::get_instance();
89
90 QString sFilename = pNode->read_string( "filename", "", false, false, bSilent );
91 QString sAbsoluteFilename = sFilename;
92
93 if ( ! Filesystem::file_exists( sFilename, true ) && ! sDrumkitPath.isEmpty() &&
94 ! sFilename.startsWith( "/" ) ) {
95
96#ifdef H2CORE_HAVE_OSC
97 if ( pHydrogen->isUnderSessionManagement() ) {
98 // If we use the NSM support and the sample files to save
99 // are corresponding to the drumkit linked/located in the
100 // session folder, we have to ensure the relative paths
101 // are loaded. This is vital in order to support
102 // renaming, duplicating, and porting sessions.
103
104 // QFileInfo::isRelative() can not be used in here as
105 // samples within drumkits within the user or system
106 // drumkit folder are stored relatively as well (by saving
107 // just the filename).
108 if ( sFilename.left( 2 ) == "./" ||
109 sFilename.left( 2 ) == ".\\" ) {
110 // Removing the leading "." of the relative path in
111 // sFilename while still using the associated folder
112 // separator.
113 sAbsoluteFilename = NsmClient::get_instance()->getSessionFolderPath() +
114 sFilename.right( sFilename.size() - 1 );
115 }
116 else {
117 sFilename = sDrumkitPath + "/" + sFilename;
118 sAbsoluteFilename = sFilename;
119 }
120 }
121 else {
122 sFilename = sDrumkitPath + "/" + sFilename;
123 sAbsoluteFilename = sFilename;
124 }
125#else
126 sFilename = sDrumkitPath + "/" + sFilename;
127 sAbsoluteFilename = sFilename;
128#endif
129 }
130
131 std::shared_ptr<Sample> pSample = nullptr;
132 if ( Filesystem::file_exists( sAbsoluteFilename, true ) ) {
133 pSample = std::make_shared<Sample>( sFilename, drumkitLicense );
134
135 // If 'ismodified' is not present, InstrumentLayer was stored as
136 // part of a drumkit. All the additional Sample info, like Loops,
137 // envelopes etc., were not written to disk and we won't load the
138 // sample.
139 bool bIsModified = pNode->read_bool( "ismodified", false, true, false, true );
140 pSample->set_is_modified( bIsModified );
141
142 if ( bIsModified ) {
143
144 Sample::Loops loops;
145 loops.mode = Sample::parse_loop_mode( pNode->read_string( "smode", "forward", false, false, bSilent ) );
146 loops.start_frame = pNode->read_int( "startframe", 0, false, false, bSilent );
147 loops.loop_frame = pNode->read_int( "loopframe", 0, false, false, bSilent );
148 loops.count = pNode->read_int( "loops", 0, false, false, bSilent );
149 loops.end_frame = pNode->read_int( "endframe", 0, false, false, bSilent );
150 pSample->set_loops( loops );
151
152 Sample::Rubberband rubberband;
153 rubberband.use = pNode->read_int( "userubber", 0, false, false, bSilent );
154 rubberband.divider = pNode->read_float( "rubberdivider", 0.0, false, false, bSilent );
155 rubberband.c_settings = pNode->read_int( "rubberCsettings", 1, false, false, bSilent );
156 rubberband.pitch = pNode->read_float( "rubberPitch", 0.0, false, false, bSilent );
157
158 // Check whether the rubberband executable is present.
160 m_rubberBandCLIexecutable ) ) {
161 rubberband.use = false;
162 }
163 pSample->set_rubberband( rubberband );
164
165 // FIXME, kill EnvelopePoint, create Envelope class
166 EnvelopePoint pt;
167
168 Sample::VelocityEnvelope velocityEnvelope;
169 XMLNode volumeNode = pNode->firstChildElement( "volume" );
170 while ( ! volumeNode.isNull() ) {
171 pt.frame = volumeNode.read_int( "volume-position", 0, false, false, bSilent );
172 pt.value = volumeNode.read_int( "volume-value", 0, false, false , bSilent);
173 velocityEnvelope.push_back( pt );
174 volumeNode = volumeNode.nextSiblingElement( "volume" );
175 }
176 pSample->set_velocity_envelope( velocityEnvelope );
177
178 Sample::VelocityEnvelope panEnvelope;
179 XMLNode panNode = pNode->firstChildElement( "pan" );
180 while ( ! panNode.isNull() ) {
181 pt.frame = panNode.read_int( "pan-position", 0, false, false, bSilent );
182 pt.value = panNode.read_int( "pan-value", 0, false, false, bSilent );
183 panEnvelope.push_back( pt );
184 panNode = panNode.nextSiblingElement( "pan" );
185 }
186 pSample->set_pan_envelope( panEnvelope );
187 }
188 }
189
190 auto pLayer = std::make_shared<InstrumentLayer>( pSample );
191 pLayer->set_start_velocity( pNode->read_float( "min", 0.0,
192 true, true, bSilent ) );
193 pLayer->set_end_velocity( pNode->read_float( "max", 1.0,
194 true, true, bSilent ) );
195 pLayer->set_gain( pNode->read_float( "gain", 1.0,
196 true, false, bSilent ) );
197 pLayer->set_pitch( pNode->read_float( "pitch", 0.0,
198 true, false, bSilent ) );
199 return pLayer;
200}
201
202void InstrumentLayer::save_to( XMLNode* node, bool bFull )
203{
204 auto pHydrogen = Hydrogen::get_instance();
205 auto pSample = get_sample();
206 if ( pSample == nullptr ) {
207 ERRORLOG( "No sample associated with layer. Skipping it" );
208 return;
209 }
210
211 XMLNode layer_node = node->createNode( "layer" );
212
213 QString sFilename;
214 if ( bFull ) {
215
216 if ( pHydrogen->isUnderSessionManagement() ) {
217 // If we use the NSM support and the sample files to save
218 // are corresponding to the drumkit linked/located in the
219 // session folder, we have to ensure the relative paths
220 // are written out. This is vital in order to support
221 // renaming, duplicating, and porting sessions.
222 if ( pSample->get_raw_filepath().startsWith( '.' ) ) {
223 sFilename = pSample->get_raw_filepath();
224 }
225 else {
226 sFilename = Filesystem::prepare_sample_path( pSample->get_filepath() );
227 }
228 }
229 else {
230 sFilename = Filesystem::prepare_sample_path( pSample->get_filepath() );
231 }
232 }
233 else {
234 sFilename = pSample->get_filename();
235 }
236
237 layer_node.write_string( "filename", sFilename );
238 layer_node.write_float( "min", __start_velocity );
239 layer_node.write_float( "max", __end_velocity );
240 layer_node.write_float( "gain", __gain );
241 layer_node.write_float( "pitch", __pitch );
242
243 if ( bFull ) {
244 layer_node.write_bool( "ismodified", pSample->get_is_modified() );
245 layer_node.write_string( "smode", pSample->get_loop_mode_string() );
246
247 Sample::Loops loops = pSample->get_loops();
248 layer_node.write_int( "startframe", loops.start_frame );
249 layer_node.write_int( "loopframe", loops.loop_frame );
250 layer_node.write_int( "loops", loops.count );
251 layer_node.write_int( "endframe", loops.end_frame );
252
253 Sample::Rubberband rubberband = pSample->get_rubberband();
254 layer_node.write_int( "userubber", static_cast<int>(rubberband.use) );
255 layer_node.write_float( "rubberdivider", rubberband.divider );
256 layer_node.write_int( "rubberCsettings", rubberband.c_settings );
257 layer_node.write_float( "rubberPitch", rubberband.pitch );
258
259 for ( const auto& velocity : *pSample->get_velocity_envelope() ) {
260 XMLNode volumeNode = layer_node.createNode( "volume" );
261 volumeNode.write_int( "volume-position", velocity.frame );
262 volumeNode.write_int( "volume-value", velocity.value );
263 }
264
265 for ( const auto& pan : *pSample->get_pan_envelope() ) {
266 XMLNode panNode = layer_node.createNode( "pan" );
267 panNode.write_int( "pan-position", pan.frame );
268 panNode.write_int( "pan-value", pan.value );
269 }
270 }
271}
272
273QString InstrumentLayer::toQString( const QString& sPrefix, bool bShort ) const {
274 QString s = Base::sPrintIndention;
275 QString sOutput;
276 if ( ! bShort ) {
277 sOutput = QString( "%1[InstrumentLayer]\n" ).arg( sPrefix )
278 .append( QString( "%1%2gain: %3\n" ).arg( sPrefix ).arg( s ).arg( __gain ) )
279 .append( QString( "%1%2pitch: %3\n" ).arg( sPrefix ).arg( s ).arg( __pitch ) )
280 .append( QString( "%1%2start_velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __start_velocity ) )
281 .append( QString( "%1%2end_velocity: %3\n" ).arg( sPrefix ).arg( s ).arg( __end_velocity ) );
282 if ( __sample != nullptr ) {
283 sOutput.append( QString( "%1" )
284 .arg( __sample->toQString( sPrefix + s, bShort ) ) );
285 } else {
286 sOutput.append( QString( "%1%2sample: nullptr\n" ).arg( sPrefix ).arg( s ) );
287 }
288 }
289 else {
290 sOutput = QString( "[InstrumentLayer]" )
291 .append( QString( " gain: %1" ).arg( __gain ) )
292 .append( QString( ", pitch: %1" ).arg( __pitch ) )
293 .append( QString( ", start_velocity: %1" ).arg( __start_velocity ) )
294 .append( QString( ", end_velocity: %1" ).arg( __end_velocity ) );
295 if ( __sample != nullptr ) {
296 sOutput.append( QString( ", sample: %1\n" ).arg( __sample->get_filepath() ) );
297 } else {
298 sOutput.append( QString( ", sample: nullptr\n" ) );
299 }
300 }
301
302 return sOutput;
303}
304
305};
306
307/* vim: set softtabstop=4 noexpandtab: */
#define ERRORLOG(x)
Definition Object.h:239
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:83
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 __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
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
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:664
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: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
void write_float(const QString &node, const float value)
write a float into a child node
Definition Xml.cpp:280
XMLNode createNode(const QString &name)
create a new XMLNode that has to be appended into de XMLDoc
Definition Xml.cpp:63
void write_string(const QString &node, const QString &value)
write a string into a child node
Definition Xml.cpp:269
void write_bool(const QString &node, const bool value)
write a boolean into a child node
Definition Xml.cpp:288
void write_int(const QString &node, const int value)
write an integer into a child node
Definition Xml.cpp:284
static NsmClient * get_instance()
Definition NsmClient.h:84
QString getSessionFolderPath() const
Definition NsmClient.h:335