hydrogen 1.2.3
Sample.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
24
25#include <limits>
26#include <memory>
27
28#include <core/Hydrogen.h>
31#include <core/Basics/Sample.h>
32#include <core/Basics/Note.h>
33
34#if defined(H2CORE_HAVE_RUBBERBAND) || _DOXYGEN_
35#include <rubberband/RubberBandStretcher.h>
36#define RUBBERBAND_BUFFER_OVERSIZE 500
37#define RUBBERBAND_DEBUG 0
38#endif
39
40namespace H2Core
41{
42
43const std::vector<QString> Sample::__loop_modes = { "forward", "reverse", "pingpong" };
44
45#if defined(H2CORE_HAVE_RUBBERBAND) || _DOXYGEN_
46static double compute_pitch_scale( const Sample::Rubberband& r );
47static RubberBand::RubberBandStretcher::Options compute_rubberband_options( const Sample::Rubberband& r );
48#endif
49
50
51/* EnvelopePoint */
52EnvelopePoint::EnvelopePoint() : frame( 0 ), value( 0 )
53{
54}
55
56EnvelopePoint::EnvelopePoint( int f, int v ) : frame( f ), value( v )
57{
58}
59
60EnvelopePoint::EnvelopePoint( const EnvelopePoint& other ) : Object(other), frame ( other.frame ), value ( other.value )
61{
62}
63/* EnvelopePoint */
64
65
66Sample::Sample( const QString& filepath, const License& license, int frames, int sample_rate, float* data_l, float* data_r )
67 : __filepath( filepath ),
68 __frames( frames ),
69 __sample_rate( sample_rate ),
70 __data_l( data_l ),
71 __data_r( data_r ),
72 __is_modified( false ),
73 m_license( license )
74{
75 if ( filepath.lastIndexOf( "/" ) <= 0 ) {
76 WARNINGLOG( QString( "Provided filepath [%1] does not seem like an absolute path. Sample will most probably be unable to load." ) );
77 }
78}
79
80Sample::Sample( std::shared_ptr<Sample> pOther ): Object( *pOther ),
81 __filepath( pOther->get_filepath() ),
82 __frames( pOther->get_frames() ),
83 __sample_rate( pOther->get_sample_rate() ),
84 __data_l( nullptr ),
85 __data_r( nullptr ),
86 __is_modified( pOther->get_is_modified() ),
87 __loops( pOther->__loops ),
88 __rubberband( pOther->__rubberband ),
89 m_license( pOther->m_license )
90{
91
92 __data_l = new float[__frames];
93 __data_r = new float[__frames];
94
95 // Since the third argument of memcpy takes the number of bytes,
96 // which are about to be copied, and the data is given in float,
97 // which are four bytes each, the number of copied frames
98 // `__frames` has to be multiplied by four.
99 memcpy( __data_l, pOther->get_data_l(), __frames * 4 );
100 memcpy( __data_r, pOther->get_data_r(), __frames * 4 );
101
102 PanEnvelope* pPan = pOther->get_pan_envelope();
103 for( int i=0; i<pPan->size(); i++ ) {
104 __pan_envelope.push_back( pPan->at(i) );
105 }
106
107 PanEnvelope* pVelocity = pOther->get_velocity_envelope();
108 for( int i=0; i<pVelocity->size(); i++ ) {
109 __velocity_envelope.push_back( pVelocity->at(i) );
110 }
111}
112
114{
115 if ( __data_l != nullptr ) {
116 delete[] __data_l;
117 }
118 if ( __data_r != nullptr ) {
119 delete[] __data_r;
120 }
121}
122
123void Sample::set_filename( const QString& filename )
124{
125 QFileInfo Filename = QFileInfo( filename );
126 QFileInfo Dest = QFileInfo( get_filepath() );
127 __filepath = QDir(Dest.absolutePath()).filePath( Filename.fileName() );
128}
129
130
135
136std::shared_ptr<Sample> Sample::load( const QString& sFilepath, const License& license )
137{
138 std::shared_ptr<Sample> pSample;
139
140 if( !Filesystem::file_readable( sFilepath ) ) {
141 ERRORLOG( QString( "Unable to read %1" ).arg( sFilepath ) );
142 return nullptr;
143 }
144
145 pSample = std::make_shared<Sample>( sFilepath, license );
146
147 // Samples loaded this way have no loops, rubberband, or envelopes
148 // set. Therefore, we do not have to pass a tempo in here.
149 if( !pSample->load() ) {
150 return nullptr;
151 }
152
153 return pSample;
154}
155
156bool Sample::load( float fBpm )
157{
158 // Will contain a bunch of metadata about the loaded sample.
159 SF_INFO sound_info = {0};
160
161 // Opens file in read-only mode.
162 SNDFILE* file = sf_open( get_filepath().toLocal8Bit(), SFM_READ, &sound_info );
163 if ( !file ) {
164 ERRORLOG( QString( "Error loading file %1" ).arg( get_filepath() ) );
165 return false;
166 }
167
168 // Sanity check. SAMPLE_CHANNELS is defined in
169 // core/include/hydrogen/globals.h and set to 2.
170 if ( sound_info.channels > SAMPLE_CHANNELS ) {
171 WARNINGLOG( QString( "can't handle %1 channels, only 2 will be used" ).arg( sound_info.channels ) );
172 sound_info.channels = SAMPLE_CHANNELS;
173 }
174 if ( sound_info.frames > ( std::numeric_limits<int>::max()/sound_info.channels ) ) {
175 WARNINGLOG( QString( "sample frames count (%1) and channels (%2) are too much, truncate it." ).arg( sound_info.frames ).arg( sound_info.channels ) );
176 sound_info.frames = ( std::numeric_limits<int>::max()/sound_info.channels );
177 }
178
179 // Create an array, which will hold the block of samples read
180 // from file.
181 float* buffer = new float[ sound_info.frames * sound_info.channels ];
182
183 //memset( buffer, 0, sound_info.frames *sound_info.channels );
184
185 // Read all frames into `buffer'. Libsndfile does seamlessly
186 // convert the format of the underlying data on the fly. The
187 // output will be an array of floats regardless of file's
188 // encoding (e.g. 16 bit PCM).
189 sf_count_t count = sf_read_float( file, buffer, sound_info.frames * sound_info.channels );
190 if( count==0 ){
191 WARNINGLOG( QString( "%1 is an empty sample" ).arg( get_filepath() ) );
192 }
193
194 // Deallocate the handler.
195 if ( sf_close( file ) != 0 ){
196 WARNINGLOG( QString( "Unable to close sample file %1" ).arg( get_filepath() ) );
197 }
198
199 // Flush the current content of the left and right channel and
200 // the current metadata.
201 unload();
202
203 // Save the metadata of the loaded file into private members
204 // of the Sample class.
205 __frames = sound_info.frames;
206 __sample_rate = sound_info.samplerate;
207
208 // Split the loaded frames into left and right channel.
209 // If only one channels was present in the underlying data,
210 // duplicate its content.
211 __data_l = new float[ sound_info.frames ];
212 __data_r = new float[ sound_info.frames ];
213 if ( sound_info.channels == 1 ) {
214 memcpy( __data_l, buffer, __frames * sizeof( float ) );
215 memcpy( __data_r, buffer, __frames * sizeof( float ) );
216 } else if ( sound_info.channels == SAMPLE_CHANNELS ) {
217 for ( int i = 0; i < __frames; i++ ) {
218 __data_l[i] = buffer[i * SAMPLE_CHANNELS ];
219 __data_r[i] = buffer[i * SAMPLE_CHANNELS + 1 ];
220 }
221 }
222 delete[] buffer;
223
224 // Apply modifiers (if present/altered).
225 if ( ! apply_loops() ) {
226 WARNINGLOG( "Unable to apply loops" );
227 }
229 apply_pan();
230#ifdef H2CORE_HAVE_RUBBERBAND
231 apply_rubberband( fBpm );
232#else
233 if ( ! exec_rubberband_cli( fBpm ) ) {
234 WARNINGLOG( "Unable to apply rubberband" );
235 }
236#endif
237
238 return true;
239}
240
242{
243 if( __loops.start_frame == 0 && __loops.loop_frame == 0 &&
244 __loops.end_frame == 0 && __loops.count == 0 ) {
245 // Default parameters. No looping was set by the
246 // user. Skipping.
247 return true;
248 }
249
250 if( __loops.start_frame<0 ) {
251 ERRORLOG( QString( "start_frame %1 < 0 is not allowed" ).arg( __loops.start_frame ) );
252 return false;
253 }
255 ERRORLOG( QString( "loop_frame %1 < start_frame %2 is not allowed" ).arg( __loops.loop_frame ).arg( __loops.start_frame ) );
256 return false;
257 }
259 ERRORLOG( QString( "end_frame %1 < loop_frame %2 is not allowed" ).arg( __loops.end_frame ).arg( __loops.loop_frame ) );
260 return false;
261 }
263 ERRORLOG( QString( "end_frame %1 > __frames %2 is not allowed" ).arg( __loops.end_frame ).arg( __frames ) );
264 return false;
265 }
266 if( __loops.count<0 ) {
267 ERRORLOG( QString( "count %1 < 0 is not allowed" ).arg( __loops.count ) );
268 return false;
269 }
270 //if( lo == __loops ) return true;
271
272 bool full_loop = __loops.start_frame==__loops.loop_frame;
273 int full_length = __loops.end_frame - __loops.start_frame;
274 int loop_length = __loops.end_frame - __loops.loop_frame;
275 int new_length = full_length + loop_length * __loops.count;
276
277 float* new_data_l = new float[ new_length ];
278 float* new_data_r = new float[ new_length ];
279
280 // copy full_length frames to new_data
281 if ( __loops.mode==Loops::REVERSE && ( __loops.count==0 || full_loop ) ) {
282 if( full_loop ) {
283 // copy end => start
284 for( int i=0, j=__loops.end_frame; i<full_length; i++, j-- ) {
285 new_data_l[i]=__data_l[j];
286 }
287 for( int i=0, j=__loops.end_frame; i<full_length; i++, j-- ) {
288 new_data_r[i]=__data_r[j];
289 }
290 } else {
291 // copy start => loop
292 int to_loop = __loops.loop_frame - __loops.start_frame;
293 memcpy( new_data_l, __data_l+__loops.start_frame, sizeof( float )*to_loop );
294 memcpy( new_data_r, __data_r+__loops.start_frame, sizeof( float )*to_loop );
295 // copy end => loop
296 for( int i=to_loop, j=__loops.end_frame; i<full_length; i++, j-- ) {
297 new_data_l[i]=__data_l[j];
298 }
299 for( int i=to_loop, j=__loops.end_frame; i<full_length; i++, j-- ) {
300 new_data_r[i]=__data_r[j];
301 }
302 }
303 } else {
304 // copy start => end
305 memcpy( new_data_l, __data_l+__loops.start_frame, sizeof( float )*full_length );
306 memcpy( new_data_r, __data_r+__loops.start_frame, sizeof( float )*full_length );
307 }
308 // copy the loops
309 if( __loops.count>0 ) {
310 int x = full_length;
311 bool forward = ( __loops.mode==Loops::FORWARD );
312 bool ping_pong = ( __loops.mode==Loops::PINGPONG );
313 for( int i=0; i<__loops.count; i++ ) {
314 if ( forward ) {
315 // copy loop => end
316 memcpy( &new_data_l[x], __data_l+__loops.loop_frame, sizeof( float )*loop_length );
317 memcpy( &new_data_r[x], __data_r+__loops.loop_frame, sizeof( float )*loop_length );
318 } else {
319 // copy end => loop
320 for( int i=__loops.end_frame, y=x; i>__loops.loop_frame; i--, y++ ) {
321 new_data_l[y]=__data_l[i];
322 }
323 for( int i=__loops.end_frame, y=x; i>__loops.loop_frame; i--, y++ ) {
324 new_data_r[y]=__data_r[i];
325 }
326 }
327 x+=loop_length;
328 if( ping_pong ) {
329 forward=!forward;
330 }
331 }
332 assert( x==new_length );
333 }
334 delete[] __data_l;
335 delete[] __data_r;
336 __data_l = new_data_l;
337 __data_r = new_data_r;
338 __frames = new_length;
339 __is_modified = true;
340
341 return true;
342}
343
345{
346 // TODO frame width (841) and height (91) should go out of here
347 // the VelocityEnvelope should be processed within TargetWaveDisplay
348 // so that we here have ( int frame_idx, float scale ) points
349 // but that will break the xml storage
350 if ( __velocity_envelope.size() == 0 ) {
351 return;
352 }
353
354 float inv_resolution = __frames / 841.0F;
355 for ( int i = 1; i < __velocity_envelope.size(); i++ ) {
356 float y = ( 91 - __velocity_envelope[i - 1].value ) / 91.0F;
357 float k = ( 91 - __velocity_envelope[i].value ) / 91.0F;
358 int start_frame = __velocity_envelope[i - 1].frame * inv_resolution;
359 int end_frame = __velocity_envelope[i].frame * inv_resolution;
360 if ( i == __velocity_envelope.size() -1 ) {
361 end_frame = __frames;
362 }
363 int length = end_frame - start_frame ;
364 float step = ( y - k ) / length;;
365 for ( int z = start_frame ; z < end_frame; z++ ) {
366 __data_l[z] = __data_l[z] * y;
367 __data_r[z] = __data_r[z] * y;
368 y-=step;
369 }
370 }
371
372 __is_modified = true;
373}
374
376{
377 if( __pan_envelope.size() == 0 ) {
378 return;
379 }
380
381 float inv_resolution = __frames / 841.0F;
382 for ( int i = 1; i < __pan_envelope.size(); i++ ) {
383 float y = ( 45 - __pan_envelope[i - 1].value ) / 45.0F;
384 float k = ( 45 - __pan_envelope[i].value ) / 45.0F;
385 int start_frame = __pan_envelope[i - 1].frame * inv_resolution;
386 int end_frame = __pan_envelope[i].frame * inv_resolution;
387 if ( i == __pan_envelope.size() -1 ) {
388 end_frame = __frames;
389 }
390 int length = end_frame - start_frame ;
391 float step = ( y - k ) / length;;
392 for ( int z = start_frame ; z < end_frame; z++ ) {
393 // seems wrong to modify only one channel ?!?!
394 if( y < 0 ) {
395 float k = 1 + y;
396 __data_l[z] = __data_l[z] * k;
397 __data_r[z] = __data_r[z];
398 } else if ( y > 0 ) {
399 float k = 1 - y;
400 __data_l[z] = __data_l[z];
401 __data_r[z] = __data_r[z] * k;
402 } else if( y==0 ) {
403 __data_l[z] = __data_l[z];
404 __data_r[z] = __data_r[z];
405 }
406 y-=step;
407 }
408 }
409
410 __is_modified = true;
411}
412
413void Sample::apply_rubberband( float fBpm ) {
414 // TODO see Rubberband declaration in sample.h
415#ifdef H2CORE_HAVE_RUBBERBAND
416 if( ! __rubberband.use ){
417 // Default behavior
418 return;
419 }
420
421 // compute rubberband options
422 double output_duration = 60.0 / fBpm * __rubberband.divider;
423 double time_ratio = output_duration / get_sample_duration();
424 RubberBand::RubberBandStretcher::Options options =
426 double pitch_scale = compute_pitch_scale( __rubberband );
427 // output buffer
428 //
429 // Sometimes the Rubber Band result is _way_ larger than expected,
430 // e.g. `out_buffer_size` = 1837 and retrieved frames = 11444. No
431 // idea what is going on there but it would make Hydrogen crash if
432 // not accounting for resizing the output buffer. The +10 is in
433 // place to cover the more frequent situations of a difference of
434 // just one frame.
435 int out_buffer_size = static_cast<int>( __frames * time_ratio + 0.1 + 10 );
436 // instantiate rubberband
437 RubberBand::RubberBandStretcher rubber = RubberBand::RubberBandStretcher( __sample_rate, 2, options, time_ratio, pitch_scale );
438 rubber.setDebugLevel( RUBBERBAND_DEBUG );
439 // This option will be ignored in real-time processing.
440 rubber.setExpectedInputDuration( __frames );
441
442 int retrieved = 0;
443 //int buffer_free = out_buffer_size;
444 float* out_data_l = new float[ out_buffer_size ];
445 float* out_data_r = new float[ out_buffer_size ];
446 float* out_data_l_tmp;
447 float* out_data_r_tmp;
448
449 DEBUGLOG( QString( "on %1\n\toptions\t\t: %2\n\ttime ratio\t: %3\n\tpitch\t\t: %4" ).arg( get_filename() ).arg( options ).arg( time_ratio ).arg( pitch_scale ) );
450
451 float* ibuf[2];
452 int block_size = MAX_BUFFER_SIZE;
453
454 // If the RUB button in the player control is activated and
455 // Hydrogen is told to apply Rubber Band to samples on-the-fly
456 // when encountering tempo changes, we will use Rubber Band's
457 // real-time processing mode.
458 if ( !Preferences::get_instance()->getRubberBandBatchMode() ) {
459 ibuf[0] = __data_l;
460 ibuf[1] = __data_r;
461 rubber.study( ibuf, __frames, true );
462 } else {
463 rubber.setMaxProcessSize( block_size );
464 }
465
466 // retrieve data
467 float* obuf[2];
468 int processed = 0;
469 int available = 0;
470 int nRequired = 0;
471
472 while( processed < __frames ) {
473
474 if ( !Preferences::get_instance()->getRubberBandBatchMode() ) {
475 // Ask Rubber Band how many samples it requires to produce
476 // further output.
477 nRequired = rubber.getSamplesRequired();
478 } else {
479 nRequired = block_size;
480 }
481 bool final = (processed + nRequired >= __frames);
482 int ibs = (final ? (__frames-processed) : nRequired );
483 float tempIbufL[ibs];
484 float tempIbufR[ibs];
485 for(int i = 0 ; i < ibs; i++) {
486 tempIbufL[i] = __data_l[i + processed];
487 tempIbufR[i] = __data_r[i + processed];
488 }
489 ibuf[0] = tempIbufL;
490 ibuf[1] = tempIbufR;
491 rubber.process( ibuf, ibs, final );
492 processed += ibs;
493
494 // .available() == 0 does indicate that Rubber Band requires
495 // more input samples in order to produce more output. Whether
496 // the stretching is complete will be checked after the parent
497 // while loop.
498 while( (available=rubber.available()) > 0 ) {
499
500 if ( retrieved + available > out_buffer_size ) {
501 // The buffers defined above are too small.
502 int nNewBufferSize = static_cast<int>( ( retrieved + available ) * 1.2 );
503 WARNINGLOG( QString( "Unexpected output size of stretched Rubber Band sample. Increasing output buffer from [%1] to [%2]" )
504 .arg( out_buffer_size )
505 .arg( nNewBufferSize ) );
506 out_data_l_tmp = new float[ out_buffer_size ];
507 out_data_r_tmp = new float[ out_buffer_size ];
508 memcpy( out_data_l_tmp, out_data_l, out_buffer_size * sizeof( float ) );
509 memcpy( out_data_r_tmp, out_data_r, out_buffer_size * sizeof( float ) );
510 delete [] out_data_l;
511 delete [] out_data_r;
512 out_data_l = new float[ nNewBufferSize ];
513 out_data_r = new float[ nNewBufferSize ];
514 memcpy( out_data_l, out_data_l_tmp, out_buffer_size * sizeof( float ) );
515 memcpy( out_data_r, out_data_r_tmp, out_buffer_size * sizeof( float ) );
516 delete [] out_data_l_tmp;
517 delete [] out_data_r_tmp;
518 }
519
520 obuf[0] = &out_data_l[retrieved];
521 obuf[1] = &out_data_r[retrieved];
522 int n = rubber.retrieve( obuf, available);
523
524 retrieved += n;
525 }
526
527 if( final ){
528 break;
529 }
530
531 }
532
533 // second run of stretcher to retrieve all last
534 // frames until stretcher returns -1.
535 while( (available=rubber.available())!= -1) {
536
537 if ( retrieved + available > out_buffer_size ) {
538 // The buffers defined above are too small.
539 int nNewBufferSize = static_cast<int>( ( retrieved + available ) * 1.5 );
540 WARNINGLOG( QString( "Unexpected output size of stretched Rubber Band sample. Increasing output buffer from [%1] to [%2[" )
541 .arg( out_buffer_size )
542 .arg( nNewBufferSize ) );
543 out_data_l_tmp = new float[ out_buffer_size ];
544 out_data_r_tmp = new float[ out_buffer_size ];
545 memcpy( out_data_l_tmp, out_data_l, out_buffer_size * sizeof( float ) );
546 memcpy( out_data_r_tmp, out_data_r, out_buffer_size * sizeof( float ) );
547 delete [] out_data_l;
548 delete [] out_data_r;
549 out_data_l = new float[ nNewBufferSize ];
550 out_data_r = new float[ nNewBufferSize ];
551 memcpy( out_data_l, out_data_l_tmp, out_buffer_size * sizeof( float ) );
552 memcpy( out_data_r, out_data_r_tmp, out_buffer_size * sizeof( float ) );
553 delete [] out_data_l_tmp;
554 delete [] out_data_r_tmp;
555
556 out_buffer_size = nNewBufferSize;
557 }
558
559 obuf[0] = &out_data_l[retrieved];
560 obuf[1] = &out_data_r[retrieved];
561 int n = rubber.retrieve( obuf, available);
562
563 retrieved += n;
564 }
565
566 delete [] __data_l;
567 delete [] __data_r;
568 __data_l = new float[ retrieved ];
569 __data_r = new float[ retrieved ];
570 memcpy( __data_l, out_data_l, retrieved*sizeof( float ) );
571 memcpy( __data_r, out_data_r, retrieved*sizeof( float ) );
572 delete [] out_data_l;
573 delete [] out_data_r;
574
575 // update sample
576 __frames = retrieved;
577 __is_modified = true;
578#endif
579}
580
582{
583 if ( ! __rubberband.use ) {
584 // Default behavior
585 return true;
586 }
587
588 //set the path to rubberband-cli
590 //test the path. if test fails return NULL
591 if ( QFile( program ).exists() == false && __rubberband.use ) {
592 ERRORLOG( QString( "Rubberband executable: File %1 not found" ).arg( program ) );
593 return false;
594 }
595
596 QString outfilePath = QDir::tempPath() + "/tmp_rb_outfile.wav";
597 if( !write( outfilePath ) ) {
598 ERRORLOG( "unable to write sample" );
599 return false;
600 };
601
602 unsigned rubberoutframes = 0;
603 double ratio = 1.0;
604 double durationtime = 60.0 / fBpm * __rubberband.divider/*beats*/;
605 double induration = get_sample_duration();
606 if ( induration != 0.0 ) {
607 ratio = durationtime / induration;
608 }
609
610 rubberoutframes = int( __frames * ratio + 0.1 );
611 _INFOLOG( QString( "ratio: %1, rubberoutframes: %2, rubberinframes: %3" ).arg( ratio ).arg ( rubberoutframes ).arg ( __frames ) );
612
613 QObject* pParent = nullptr;
614 QProcess* pRubberbandProc = new QProcess( pParent );
615
616 QStringList arguments;
617 QString rCs = QString( " %1" ).arg( __rubberband.c_settings );
618 float fFrequency = Note::pitchToFrequency( ( double )__rubberband.pitch );
619 QString rFs = QString( " %1" ).arg( fFrequency );
620 QString rubberResultPath = QDir::tempPath() + "/tmp_rb_result_file.wav";
621
622 arguments << "-D" << QString( " %1" ).arg( durationtime ) //stretch or squash to make output file X seconds long
623 << "--threads" //assume multi-CPU even if only one CPU is identified
624 << "-P" //aim for minimal time distortion
625 << "-f" << rFs //frequency
626 << "-c" << rCs //"crispness" levels
627 << outfilePath //infile
628 << rubberResultPath; //outfile
629
630 pRubberbandProc->start( program, arguments );
631
632 while( pRubberbandProc->state() != QProcess::NotRunning
633 && !pRubberbandProc->waitForFinished() ) {
634 //_ERRORLOG( QString( "processing" ));
635 }
636
637 delete pRubberbandProc;
638 if ( QFile( rubberResultPath ).exists() == false ) {
639 _ERRORLOG( QString( "Rubberband reimporter File %1 not found" ).arg( rubberResultPath ) );
640 return false;
641 }
642
643 auto p_Rubberbanded = Sample::load( rubberResultPath );
644 if( p_Rubberbanded == nullptr ) {
645 return false;
646 }
647
648 QFile( outfilePath ).remove();
649
650 QFile( rubberResultPath ).remove();
651
652 __frames = p_Rubberbanded->get_frames();
653
654 __data_l = p_Rubberbanded->get_data_l();
655 __data_r = p_Rubberbanded->get_data_r();
656 p_Rubberbanded->__data_l = nullptr;
657 p_Rubberbanded->__data_r = nullptr;
658
659 __is_modified = true;
660
661 return true;
662}
663
665{
666 if ( sMode == "forward" ) {
667 return Loops::FORWARD;
668 } else if ( sMode == "reverse" ) {
669 return Loops::REVERSE;
670 } else if ( sMode == "pingpong" ) {
671 return Loops::PINGPONG;
672 }
673
674 return Loops::FORWARD;
675}
676
677bool Sample::write( const QString& path, int format )
678{
679 float* obuf = new float[ SAMPLE_CHANNELS * __frames ];
680 for ( int i = 0; i < __frames; ++i ) {
681 float value_l = __data_l[i];
682 float value_r = __data_r[i];
683
684 if ( value_l > 1.f ) {
685 value_l = 1.f;
686 } else if ( value_l < -1.f ) {
687 value_l = -1.f;
688 } else if ( value_r > 1.f ) {
689 value_r = 1.f;
690 } else if ( value_r < -1.f ) {
691 value_r = -1.f;
692 }
693
694 obuf[ i* SAMPLE_CHANNELS + 0 ] = value_l;
695 obuf[ i* SAMPLE_CHANNELS + 1 ] = value_r;
696 }
697 SF_INFO sf_info;
698 sf_info.channels = SAMPLE_CHANNELS;
699 sf_info.frames = __frames;
700 sf_info.samplerate = __sample_rate;
701 sf_info.format = format;
702 if ( !sf_format_check( &sf_info ) ) {
703 ___ERRORLOG( "SF_INFO error" );
704 delete[] obuf;
705 return false;
706 }
707
708 SNDFILE* sf_file = sf_open( path.toLocal8Bit().data(), SFM_WRITE, &sf_info ) ;
709
710 if ( sf_file==nullptr ) {
711 ___ERRORLOG( QString( "sf_open error : %1" ).arg( sf_strerror( sf_file ) ) );
712 sf_close( sf_file );
713 delete[] obuf;
714 return false;
715 }
716
717 sf_count_t res = sf_writef_float( sf_file, obuf, __frames );
718
719 if ( res<=0 ) {
720 ___ERRORLOG( QString( "sf_writef_float error : %1" ).arg( sf_strerror( sf_file ) ) );
721 sf_close( sf_file );
722 delete[] obuf;
723 return false;
724 }
725
726 sf_close( sf_file );
727 delete[] obuf;
728 return true;
729}
730
731QString Sample::Loops::toQString( const QString& sPrefix, bool bShort ) const {
732 QString s = Base::sPrintIndention;
733 QString sOutput;
734 if ( ! bShort ) {
735 sOutput = QString( "%1[Loops]\n" ).arg( sPrefix )
736 .append( QString( "%1%2start_frame: %3\n" ).arg( sPrefix ).arg( s ).arg( start_frame ) )
737 .append( QString( "%1%2loop_frame: %3\n" ).arg( sPrefix ).arg( s ).arg( loop_frame ) )
738 .append( QString( "%1%2end_frame: %3\n" ).arg( sPrefix ).arg( s ).arg( end_frame ) )
739 .append( QString( "%1%2count: %3\n" ).arg( sPrefix ).arg( s ).arg( count ) )
740 .append( QString( "%1%2mode: %3\n" ).arg( sPrefix ).arg( s ).arg( mode ) );
741 } else {
742 sOutput = QString( "[Loops]" )
743 .append( QString( " start_frame: %1" ).arg( start_frame ) )
744 .append( QString( ", loop_frame: %1" ).arg( loop_frame ) )
745 .append( QString( ", end_frame: %1" ).arg( end_frame ) )
746 .append( QString( ", count: %1" ).arg( count ) )
747 .append( QString( ", mode: %1" ).arg( mode ) );
748 }
749
750 return sOutput;
751}
752
753QString Sample::Rubberband::toQString( const QString& sPrefix, bool bShort ) const {
754 QString s = Base::sPrintIndention;
755 QString sOutput;
756 if ( ! bShort ) {
757 sOutput = QString( "%1[Rubberband]\n" ).arg( sPrefix )
758 .append( QString( "%1%2use: %3\n" ).arg( sPrefix ).arg( s ).arg( use ) )
759 .append( QString( "%1%2divider: %3\n" ).arg( sPrefix ).arg( s ).arg( divider ) )
760 .append( QString( "%1%2pitch: %3\n" ).arg( sPrefix ).arg( s ).arg( pitch ) )
761 .append( QString( "%1%2c_settings: %3\n" ).arg( sPrefix ).arg( s ).arg( c_settings ) );
762 } else {
763 sOutput = QString( "[Rubberband]" )
764 .append( QString( " use: %1" ).arg( use ) )
765 .append( QString( ", divider: %1" ).arg( divider ) )
766 .append( QString( ", pitch: %1" ).arg( pitch ) )
767 .append( QString( ", c_settings: %1" ).arg( c_settings ) );
768 }
769 return sOutput;
770}
771
772QString Sample::toQString( const QString& sPrefix, bool bShort ) const {
773 QString s = Base::sPrintIndention;
774 QString sOutput;
775 if ( ! bShort ) {
776 sOutput = QString( "%1[Sample]\n" ).arg( sPrefix )
777 .append( QString( "%1%2filepath: %3\n" ).arg( sPrefix ).arg( s ).arg( __filepath ) )
778 .append( QString( "%1%2frames: %3\n" ).arg( sPrefix ).arg( s ).arg( __frames ) )
779 .append( QString( "%1%2sample_rate: %3\n" ).arg( sPrefix ).arg( s ).arg( __sample_rate ) )
780 .append( QString( "%1%2is_modified: %3\n" ).arg( sPrefix ).arg( s ).arg( __is_modified ) )
781 .append( QString( "%1%2m_license: %3\n" ).arg( sPrefix ).arg( s ).arg( m_license.toQString() ) )
782 .append( QString( "%1" ).arg( __loops.toQString( sPrefix + s, bShort ) ) )
783 .append( QString( "%1" ).arg( __rubberband.toQString( sPrefix + s, bShort ) ) );
784 } else {
785 sOutput = QString( "[Sample]" )
786 .append( QString( " filepath: %1" ).arg( __filepath ) )
787 .append( QString( ", frames: %1" ).arg( __frames ) )
788 .append( QString( ", sample_rate: %1" ).arg( __sample_rate ) )
789 .append( QString( ", is_modified: %1" ).arg( __is_modified ) )
790 .append( QString( ", m_license: %1" ).arg( m_license.toQString() ) )
791 .append( QString( ", [%1]" ).arg( __loops.toQString( sPrefix + s, bShort ) ) )
792 .append( QString( ", [%1]\n" ).arg( __rubberband.toQString( sPrefix + s, bShort ) ) );
793 }
794
795 return sOutput;
796}
797
798#ifdef H2CORE_HAVE_RUBBERBAND
799static double compute_pitch_scale( const Sample::Rubberband& rb )
800{
801 return Note::pitchToFrequency( rb.pitch );
802}
803
804static RubberBand::RubberBandStretcher::Options compute_rubberband_options( const Sample::Rubberband& rb )
805{
806 // default settings
807 enum {
808 CompoundDetector,
809 PercussiveDetector,
810 SoftDetector
811 } detector = CompoundDetector;
812 enum {
813 NoTransients,
814 BandLimitedTransients,
815 Transients
816 } transients = Transients;
817 bool lamination = true;
818 bool longwin = false;
819 bool shortwin = false;
820 RubberBand::RubberBandStretcher::Options options = RubberBand::RubberBandStretcher::DefaultOptions;
821 // apply our settings
822 int crispness = rb.c_settings;
823 // compute result options
824 switch ( crispness ) {
825 case -1:
826 crispness = 5;
827 break;
828 case 0:
829 detector = CompoundDetector;
830 transients = NoTransients;
831 lamination = false;
832 longwin = true;
833 shortwin = false;
834 break;
835 case 1:
836 detector = SoftDetector;
837 transients = Transients;
838 lamination = false;
839 longwin = true;
840 shortwin = false;
841 break;
842 case 2:
843 detector = CompoundDetector;
844 transients = NoTransients;
845 lamination = false;
846 longwin = false;
847 shortwin = false;
848 break;
849 case 3:
850 detector = CompoundDetector;
851 transients = NoTransients;
852 lamination = true;
853 longwin = false;
854 shortwin = false;
855 break;
856 case 4:
857 detector = CompoundDetector;
858 transients = BandLimitedTransients;
859 lamination = true;
860 longwin = false;
861 shortwin = false;
862 break;
863 case 5:
864 detector = CompoundDetector;
865 transients = Transients;
866 lamination = true;
867 longwin = false;
868 shortwin = false;
869 break;
870 case 6:
871 detector = CompoundDetector;
872 transients = Transients;
873 lamination = false;
874 longwin = false;
875 shortwin = true;
876 break;
877 };
878
879 if ( Preferences::get_instance()->getRubberBandBatchMode() ) {
880 options |= RubberBand::RubberBandStretcher::OptionProcessRealTime;
881 } else {
882 options |= RubberBand::RubberBandStretcher::OptionProcessOffline;
883 }
884
885 if ( !lamination ) options |= RubberBand::RubberBandStretcher::OptionPhaseIndependent;
886 if ( longwin ) options |= RubberBand::RubberBandStretcher::OptionWindowLong;
887 if ( shortwin ) options |= RubberBand::RubberBandStretcher::OptionWindowShort;
888 options |= RubberBand::RubberBandStretcher::OptionStretchPrecise;
889 //if (smoothing) options |= RubberBand::RubberBandStretcher::OptionSmoothingOn;
890 //if (formant) options |= RubberBand::RubberBandStretcher::OptionFormantPreserved;
891 //if (hqpitch) options |= RubberBand::RubberBandStretcher::OptionPitchHighQuality;
892 options |= RubberBand::RubberBandStretcher::OptionPitchHighQuality;
893 /*
894 switch (threading) {
895 case 0:
896 options |= RubberBand::RubberBandStretcher::OptionThreadingAuto;
897 break;
898 case 1:
899 options |= RubberBand::RubberBandStretcher::OptionThreadingNever;
900 break;
901 case 2:
902 options |= RubberBand::RubberBandStretcher::OptionThreadingAlways;
903 break;
904 }
905 */
906 switch ( transients ) {
907 case NoTransients:
908 options |= RubberBand::RubberBandStretcher::OptionTransientsSmooth;
909 break;
910 case BandLimitedTransients:
911 options |= RubberBand::RubberBandStretcher::OptionTransientsMixed;
912 break;
913 case Transients:
914 options |= RubberBand::RubberBandStretcher::OptionTransientsCrisp;
915 break;
916 }
917 /*
918 switch (detector) {
919 case CompoundDetector:
920 options |= RubberBand::RubberBandStretcher::OptionDetectorCompound;
921 break;
922 case PercussiveDetector:
923 options |= RubberBand::RubberBandStretcher::OptionDetectorPercussive;
924 break;
925 case SoftDetector:
926 options |= RubberBand::RubberBandStretcher::OptionDetectorSoft;
927 break;
928 }
929 */
930 return options;
931}
932#endif
933
934};
935
936/* vim: set softtabstop=4 noexpandtab: */
#define _INFOLOG(x)
Definition Object.h:243
#define WARNINGLOG(x)
Definition Object.h:238
#define ERRORLOG(x)
Definition Object.h:239
#define _ERRORLOG(x)
Definition Object.h:245
#define DEBUGLOG(x)
Definition Object.h:236
#define ___ERRORLOG(x)
Definition Object.h:257
#define RUBBERBAND_DEBUG
Definition Sample.cpp:37
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
EnvelopePoint()
default constructor
Definition Sample.cpp:52
static QString ensure_session_compatibility(const QString &sPath)
If Hydrogen is under session management, we support for paths relative to the session folder.
static bool file_readable(const QString &path, bool silent=false)
returns true if the given path is an existing readable regular file
Wrapper class to help Hydrogen deal with the license information specified in a drumkit.
Definition License.h:48
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
Definition License.cpp:151
static double pitchToFrequency(double fPitch)
Convert a logarithmic pitch-space value in semitones to a frequency-domain value.
Definition Note.h:388
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
QString m_rubberBandCLIexecutable
Rubberband CLI.
QString toQString(const QString &sPrefix="", bool bShort=true) const
Definition Sample.cpp:731
LoopMode
possible sample editing loop mode
Definition Sample.h:81
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
QString toQString(const QString &sPrefix="", bool bShort=true) const
Definition Sample.cpp:753
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
int __frames
number of frames in this sample
Definition Sample.h:307
bool __is_modified
true if sample is modified
Definition Sample.h:311
float * __data_l
left channel data
Definition Sample.h:309
bool apply_loops()
apply __loops transformation to the sample
Definition Sample.cpp:241
QString get_filepath() const
Definition Sample.cpp:131
void apply_pan()
apply __pan_envelope transformation to the sample
Definition Sample.cpp:375
int __sample_rate
samplerate for this sample
Definition Sample.h:308
static Loops::LoopMode parse_loop_mode(const QString &string)
parse the given string and rturn the corresponding loop_mode
Definition Sample.cpp:664
Rubberband __rubberband
set of rubberband parameters
Definition Sample.h:315
void unload()
Flush the current content of the left and right channel and the current metadata.
Definition Sample.h:336
double get_sample_duration() const
Definition Sample.h:392
const QString get_filename() const
Definition Sample.h:367
~Sample()
destructor
Definition Sample.cpp:113
Loops __loops
set of loop parameters
Definition Sample.h:314
License m_license
Transient property indicating the license associated with the sample.
Definition Sample.h:331
float * __data_r
right channel data
Definition Sample.h:310
static std::shared_ptr< Sample > load(const QString &filepath, const License &license=License())
Definition Sample.cpp:136
bool exec_rubberband_cli(float fBpm)
call rubberband cli to modify the sample using __rubberband
Definition Sample.cpp:581
VelocityEnvelope __velocity_envelope
velocity envelope vector
Definition Sample.h:313
Sample(const QString &filepath, const License &license=License(), int frames=0, int sample_rate=0, float *data_l=nullptr, float *data_r=nullptr)
Sample constructor.
Definition Sample.cpp:66
void apply_velocity()
apply __velocity_envelope transformation to the sample
Definition Sample.cpp:344
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
Definition Sample.cpp:772
QString __filepath
filepath of the sample
Definition Sample.h:306
bool write(const QString &path, int format=(SF_FORMAT_WAV|SF_FORMAT_PCM_16))
write sample to a file
Definition Sample.cpp:677
PanEnvelope __pan_envelope
pan envelope vector
Definition Sample.h:312
void apply_rubberband(float fBpm)
apply __rubberband transformation to the sample
Definition Sample.cpp:413
std::vector< EnvelopePoint > PanEnvelope
define the type used to store pan envelope points
Definition Sample.h:73
static const std::vector< QString > __loop_modes
loop modes string
Definition Sample.h:317
void set_filename(const QString &filename)
Definition Sample.cpp:123
#define MAX_BUFFER_SIZE
Maximum buffer size.
Definition config.dox:87
#define SAMPLE_CHANNELS
Definition Globals.h:38
static double compute_pitch_scale(const Sample::Rubberband &r)
static RubberBand::RubberBandStretcher::Options compute_rubberband_options(const Sample::Rubberband &r)