diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 181956ea9..eeda62991 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -46,6 +46,11 @@ class EXPORT SampleBuffer : public QObject, public sharedObject { Q_OBJECT public: + enum LoopMode { + LoopOff = 0, + LoopOn, + LoopPingPong + }; class EXPORT handleState { public: @@ -62,11 +67,21 @@ public: m_frameIndex = _index; } + inline bool isBackwards() const + { + return m_isBackwards; + } + + inline void setBackwards( bool _backwards ) + { + m_isBackwards = _backwards; + } private: f_cnt_t m_frameIndex; const bool m_varyingPitch; + bool m_isBackwards; SRC_STATE * m_resamplingData; friend class SampleBuffer; @@ -86,7 +101,7 @@ public: bool play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, const float _freq, - const bool _looped = false ); + const LoopMode _loopmode = LoopOff ); void visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0 ); inline void visualize( QPainter & _p, const QRect & _dr, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0 ) @@ -108,7 +123,7 @@ public: { return m_endFrame; } - + inline f_cnt_t loopStartFrame() const { return m_loopStartFrame; @@ -272,10 +287,13 @@ private: float m_frequency; sample_rate_t m_sampleRate; - sampleFrame * getSampleFragment( f_cnt_t _start, f_cnt_t _frames, - bool _looped, - sampleFrame * * _tmp ) const; - f_cnt_t getLoopedIndex( f_cnt_t _index ) const; + sampleFrame * getSampleFragment( f_cnt_t _index, f_cnt_t _frames, + LoopMode _loopmode, + sampleFrame * * _tmp, + bool * _backwards, f_cnt_t _loopstart, f_cnt_t _loopend, + f_cnt_t _end ) const; + f_cnt_t getLoopedIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const; + f_cnt_t getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const; signals: diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index a70fe52a4..d285b4eaa 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -2,7 +2,7 @@ * audio_file_processor.cpp - instrument for using audio-files * * Copyright (c) 2004-2014 Tobias Doerffel - * + * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or @@ -76,9 +76,10 @@ audioFileProcessor::audioFileProcessor( InstrumentTrack * _instrument_track ) : m_endPointModel( 1, 0, 1, 0.0000001f, this, tr( "End of sample" ) ), m_loopPointModel( 0, 0, 1, 0.0000001f, this, tr( "Loopback point" ) ), m_reverseModel( false, this, tr( "Reverse sample" ) ), - m_loopModel( false, this, tr( "Loop enabled" ) ), + m_loopModel( 0, 0, 2, this, tr( "Loop mode" ) ), m_stutterModel( false, this, tr( "Stutter" ) ), - m_nextPlayStartPoint( 0 ) + m_nextPlayStartPoint( 0 ), + m_nextPlayBackwards( false ) { connect( &m_reverseModel, SIGNAL( dataChanged() ), this, SLOT( reverseModelChanged() ) ); @@ -116,6 +117,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, if( m_stutterModel.value() == true && _n->frequency() < 20.0 ) { m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayBackwards = false; return; } @@ -126,9 +128,11 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, // Restart playing the note if in stutter mode, not in loop mode, // and we're at the end of the sample. m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayBackwards = false; } _n->m_pluginData = new handleState( _n->hasDetuningInfo() ); ((handleState *)_n->m_pluginData)->setFrameIndex( m_nextPlayStartPoint ); + ((handleState *)_n->m_pluginData)->setBackwards( m_nextPlayBackwards ); // debug code /* qDebug( "frames %d", m_sampleBuffer.frames() ); @@ -141,7 +145,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, if( m_sampleBuffer.play( _working_buffer, (handleState *)_n->m_pluginData, frames, _n->frequency(), - m_loopModel.value() ) ) + static_cast( m_loopModel.value() ) ) ) { applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, @@ -161,6 +165,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, if( m_stutterModel.value() == true ) { m_nextPlayStartPoint = ((handleState *)_n->m_pluginData)->frameIndex(); + m_nextPlayBackwards = ((handleState *)_n->m_pluginData)->isBackwards(); } } @@ -214,7 +219,7 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) m_ampModel.loadSettings( _this, "amp" ); m_startPointModel.loadSettings( _this, "sframe" ); m_endPointModel.loadSettings( _this, "eframe" ); - + // compat code for not having a separate loopback point if( _this.hasAttribute( "lframe" ) ) { @@ -307,6 +312,7 @@ void audioFileProcessor::ampModelChanged( void ) void audioFileProcessor::stutterModelChanged() { m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayBackwards = false; } @@ -321,28 +327,38 @@ void audioFileProcessor::loopPointChanged( void ) m_endPointModel.setValue( m_startPointModel.value() ); m_startPointModel.setValue( tmp ); } - + // check if start & end overlap and nudge end up if so if( m_startPointModel.value() == m_endPointModel.value() ) { m_endPointModel.setValue( qMin( m_endPointModel.value() + 0.001f, 1.0f ) ); } - - const f_cnt_t f_start = static_cast( m_startPointModel.value() * - ( m_sampleBuffer.frames()-1 ) ); - const f_cnt_t f_end = static_cast( m_endPointModel.value() * - ( m_sampleBuffer.frames()-1 ) ); - m_nextPlayStartPoint = f_start; - // check that loop point is between start-end points + // check that loop point is between start-end points and not overlapping with endpoint + // ...and move start/end points ahead if loop point is moved over them + if( m_loopPointModel.value() >= m_endPointModel.value() ) + { + m_endPointModel.setValue( m_loopPointModel.value() + 0.001f ); + if( m_endPointModel.value() == 1.0f ) + { + m_loopPointModel.setValue( 1.0f - 0.001f ); + } + } if( m_loopPointModel.value() < m_startPointModel.value() ) - m_loopPointModel.setValue( m_startPointModel.value() ); - if( m_loopPointModel.value() > m_endPointModel.value() ) - m_loopPointModel.setValue( m_endPointModel.value() ); + { + m_startPointModel.setValue( m_loopPointModel.value() ); + } + + const f_cnt_t f_start = static_cast( m_startPointModel.value() * ( m_sampleBuffer.frames()-1 ) ); + const f_cnt_t f_end = static_cast( m_endPointModel.value() * ( m_sampleBuffer.frames()-1 ) ); + const f_cnt_t f_loop = static_cast( m_loopPointModel.value() * ( m_sampleBuffer.frames()-1 ) ); + + m_nextPlayStartPoint = f_start; + m_nextPlayBackwards = false; m_sampleBuffer.setStartFrame( f_start ); m_sampleBuffer.setEndFrame( f_end ); - m_sampleBuffer.setLoopStartFrame( static_cast( m_loopPointModel.value() * ( m_sampleBuffer.frames()-1 ) ) ); + m_sampleBuffer.setLoopStartFrame( f_loop ); m_sampleBuffer.setLoopEndFrame( f_end ); emit dataChanged(); } @@ -377,13 +393,13 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument, m_openAudioFileButton->setWhatsThis( tr( "Click here, if you want to open another audio-file. " "A dialog will appear where you can select your file. " - "Settings like looping-mode, start and end-points, " + "Settings like looping-mode, start and end-points, " "amplify-value, and so on are not reset. So, it may not " "sound like the original sample.") ); m_reverseButton = new pixmapButton( this ); m_reverseButton->setCheckable( TRUE ); - m_reverseButton->move( 174, 124 ); + m_reverseButton->move( 200, 124 ); m_reverseButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "reverse_on" ) ); m_reverseButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( @@ -394,18 +410,51 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument, "This is useful for cool effects, e.g. a reversed " "crash." ) ); - m_loopButton = new pixmapButton( this ); - m_loopButton->setCheckable( TRUE ); - m_loopButton->move( 200, 124 ); - m_loopButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_on" ) ); - m_loopButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_off" ) ); - toolTip::add( m_loopButton, tr( "Enable loop" ) ); - m_loopButton->setWhatsThis( - tr( "This button enables looping. " +// loop button group + + pixmapButton * m_loopOffButton = new pixmapButton( this ); + m_loopOffButton->setCheckable( TRUE ); + m_loopOffButton->move( 174, 144 ); + m_loopOffButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( + "loop_off_on" ) ); + m_loopOffButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( + "loop_off_off" ) ); + toolTip::add( m_loopOffButton, tr( "Disable loop" ) ); + m_loopOffButton->setWhatsThis( + tr( "This button disables looping. " + "The sample plays only once from start to end. " ) ); + + + pixmapButton * m_loopOnButton = new pixmapButton( this ); + m_loopOnButton->setCheckable( TRUE ); + m_loopOnButton->move( 200, 144 ); + m_loopOnButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( + "loop_on_on" ) ); + m_loopOnButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( + "loop_on_off" ) ); + toolTip::add( m_loopOnButton, tr( "Enable loop" ) ); + m_loopOnButton->setWhatsThis( + tr( "This button enables forwards-looping. " "The sample loops between the end point and the loop point." ) ); - + + pixmapButton * m_loopPingPongButton = new pixmapButton( this ); + m_loopPingPongButton->setCheckable( TRUE ); + m_loopPingPongButton->move( 226, 144 ); + m_loopPingPongButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( + "loop_pingpong_on" ) ); + m_loopPingPongButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( + "loop_pingpong_off" ) ); + toolTip::add( m_loopPingPongButton, tr( "Enable loop" ) ); + m_loopPingPongButton->setWhatsThis( + tr( "This button enables ping-pong-looping. " + "The sample loops backwards and forwards between the end point " + "and the loop point." ) ); + + m_loopGroup = new automatableButtonGroup( this ); + m_loopGroup->addButton( m_loopOffButton ); + m_loopGroup->addButton( m_loopOnButton ); + m_loopGroup->addButton( m_loopPingPongButton ); + m_stutterButton = new pixmapButton( this ); m_stutterButton->setCheckable( true ); m_stutterButton->move( 226, 124 ); @@ -604,7 +653,7 @@ void AudioFileProcessorView::modelChanged( void ) m_endKnob->setModel( &a->m_endPointModel ); m_loopKnob->setModel( &a->m_loopPointModel ); m_reverseButton->setModel( &a->m_reverseModel ); - m_loopButton->setModel( &a->m_loopModel ); + m_loopGroup->setModel( &a->m_loopModel ); m_stutterButton->setModel( &a->m_stutterModel ); sampleUpdated(); } @@ -675,16 +724,23 @@ void AudioFileProcessorWaveView::mousePressEvent( QMouseEvent * _me ) { m_isDragging = true; m_draggingLastPoint = _me->pos(); - - const int start_dist = qAbs( m_startFrameX - _me->x() ); - const int end_dist = qAbs( m_endFrameX - _me->x() ); - const int loop_dist = qAbs( m_loopFrameX - _me->x() ); - + + const int x = _me->x(); + + const int start_dist = qAbs( m_startFrameX - x ); + const int end_dist = qAbs( m_endFrameX - x ); + const int loop_dist = qAbs( m_loopFrameX - x ); + draggingType dt = sample_loop; int md = loop_dist; if( start_dist < loop_dist ) { dt = sample_start; md = start_dist; } - if( end_dist < start_dist ) { dt = sample_end; md = end_dist; } - - if( md < 3 ) + else if( end_dist < loop_dist ) { dt = sample_end; md = end_dist; } + +/* qDebug( "x %d", x ); + qDebug( "loopframex %d", m_loopFrameX ); + qDebug( "dt %d", dt ); + qDebug( "md %d", md );*/ + + if( md < 4 ) { m_draggingType = dt; } @@ -780,7 +836,6 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) p.drawPixmap( s_padding, s_padding, m_graph ); - p.setPen( QColor( 0xFF, 0xFF, 0xFF ) ); //TODO: put into a qproperty const QRect graph_rect( s_padding, s_padding, width() - 2 * s_padding, height() - 2 * s_padding ); const f_cnt_t frames = m_to - m_from; m_startFrameX = graph_rect.x() + ( m_sampleBuffer.startFrame() - m_from ) * @@ -792,17 +847,21 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) const int played_width_px = ( m_framesPlayed - m_from ) * double( graph_rect.width() ) / frames; + // loop point line + p.setPen( QColor( 0x7F, 0xFF, 0xFF ) ); //TODO: put into a qproperty + p.drawLine( m_loopFrameX, graph_rect.y(), + m_loopFrameX, + graph_rect.height() + graph_rect.y() ); + + // start/end lines + p.setPen( QColor( 0xFF, 0xFF, 0xFF ) ); //TODO: put into a qproperty p.drawLine( m_startFrameX, graph_rect.y(), m_startFrameX, graph_rect.height() + graph_rect.y() ); p.drawLine( m_endFrameX, graph_rect.y(), m_endFrameX, graph_rect.height() + graph_rect.y() ); - - p.setPen( QColor( 0x7F, 0xFF, 0xFF ) ); //TODO: put into a qproperty - p.drawLine( m_loopFrameX, graph_rect.y(), - m_loopFrameX, - graph_rect.height() + graph_rect.y() ); + if( m_endFrameX - m_startFrameX > 2 ) { @@ -1006,7 +1065,7 @@ void AudioFileProcessorWaveView::setKnobs( knob * _start, knob * _end, knob * _l m_endKnob->setWaveView( this ); m_endKnob->setRelatedKnob( m_startKnob ); - + m_loopKnob->setWaveView( this ); } @@ -1130,7 +1189,7 @@ bool AudioFileProcessorWaveView::knob::checkBound( double _v ) const { return true; } - + if( ( m_relatedKnob->model()->value() - _v > 0 ) != ( m_relatedKnob->model()->value() - model()->value() >= 0 ) ) return false; diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index 0fd0f97f3..1f0558ab5 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -34,6 +34,7 @@ #include "SampleBuffer.h" #include "knob.h" #include "pixmap_button.h" +#include "automatable_button.h" @@ -91,10 +92,11 @@ private: FloatModel m_endPointModel; FloatModel m_loopPointModel; BoolModel m_reverseModel; - BoolModel m_loopModel; + IntModel m_loopModel; BoolModel m_stutterModel; f_cnt_t m_nextPlayStartPoint; + bool m_nextPlayBackwards; friend class AudioFileProcessorView; @@ -137,7 +139,7 @@ private: pixmapButton * m_openAudioFileButton; pixmapButton * m_reverseButton; - pixmapButton * m_loopButton; + automatableButtonGroup * m_loopGroup; pixmapButton * m_stutterButton; } ; @@ -270,7 +272,7 @@ private: static bool isCloseTo( int _a, int _b ) { - return qAbs( _a - _b ) < 3; + return qAbs( _a - _b ) < 4; } } ; diff --git a/plugins/audio_file_processor/loop_off_off.png b/plugins/audio_file_processor/loop_off_off.png new file mode 100644 index 000000000..a66e94d99 Binary files /dev/null and b/plugins/audio_file_processor/loop_off_off.png differ diff --git a/plugins/audio_file_processor/loop_off_on.png b/plugins/audio_file_processor/loop_off_on.png new file mode 100644 index 000000000..4cf0edf2e Binary files /dev/null and b/plugins/audio_file_processor/loop_off_on.png differ diff --git a/plugins/audio_file_processor/loop_off.png b/plugins/audio_file_processor/loop_on_off.png similarity index 100% rename from plugins/audio_file_processor/loop_off.png rename to plugins/audio_file_processor/loop_on_off.png diff --git a/plugins/audio_file_processor/loop_on.png b/plugins/audio_file_processor/loop_on_on.png similarity index 100% rename from plugins/audio_file_processor/loop_on.png rename to plugins/audio_file_processor/loop_on_on.png diff --git a/plugins/audio_file_processor/loop_pingpong_off.png b/plugins/audio_file_processor/loop_pingpong_off.png new file mode 100644 index 000000000..6758d2c79 Binary files /dev/null and b/plugins/audio_file_processor/loop_pingpong_off.png differ diff --git a/plugins/audio_file_processor/loop_pingpong_on.png b/plugins/audio_file_processor/loop_pingpong_on.png new file mode 100644 index 000000000..f21112c7a Binary files /dev/null and b/plugins/audio_file_processor/loop_pingpong_on.png differ diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index f5f24335f..58cf987bc 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -149,7 +149,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n, hdata->sample->frequency(); if( hdata->sample->play( _working_buffer, hdata->state, frames, - play_freq, m_loopedModel.value() ) ) + play_freq, m_loopedModel.value() ? SampleBuffer::LoopOn : SampleBuffer::LoopOff ) ) { applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 2b656cb7a..9ef094e0f 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -610,23 +610,31 @@ f_cnt_t SampleBuffer::decodeSampleDS( const char * _f, bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, const float _freq, - const bool _looped ) -{ + const LoopMode _loopmode ) +{ QMutexLocker ml( &m_varLock ); + + f_cnt_t startFrame = m_startFrame; + f_cnt_t endFrame = m_endFrame; + f_cnt_t loopStartFrame = m_loopStartFrame; + f_cnt_t loopEndFrame = m_loopEndFrame; engine::mixer()->clearAudioBuffer( _ab, _frames ); - if( m_endFrame == 0 || _frames == 0 ) + if( endFrame == 0 || _frames == 0 ) { return false; } + // variable for determining if we should currently be playing backwards in a ping-pong loop + bool is_backwards = _state->isBackwards(); + const double freq_factor = (double) _freq / (double) m_frequency * m_sampleRate / engine::mixer()->processingSampleRate(); // calculate how many frames we have in requested pitch const f_cnt_t total_frames_for_current_pitch = static_cast( ( - m_endFrame - m_startFrame ) / + endFrame - startFrame ) / freq_factor ); if( total_frames_for_current_pitch == 0 ) @@ -634,36 +642,47 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, return false; } + // this holds the number of the first frame to play f_cnt_t play_frame = _state->m_frameIndex; - if( play_frame < m_startFrame ) + + if( play_frame < startFrame ) { - play_frame = m_startFrame; + play_frame = startFrame; } // this holds the number of remaining frames in current loop f_cnt_t frames_for_loop; - if( _looped ) + if( _loopmode == LoopOn ) { - play_frame = getLoopedIndex( play_frame ); - frames_for_loop = static_cast( - ( m_loopEndFrame - play_frame ) / - freq_factor ); + play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame ); + frames_for_loop = static_cast( ( loopEndFrame - play_frame ) / freq_factor ); } + + else if( _loopmode == LoopPingPong ) + { + play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame ); + if( is_backwards ) + frames_for_loop = static_cast( ( play_frame - loopStartFrame ) / freq_factor ); + else + frames_for_loop = static_cast( ( loopEndFrame - play_frame ) / freq_factor ); + } + else { - if( play_frame >= m_endFrame ) + if( play_frame >= endFrame ) { return false; } - frames_for_loop = static_cast( - ( m_endFrame - play_frame ) / - freq_factor ); + + frames_for_loop = static_cast( ( endFrame - play_frame ) / freq_factor ); + if( frames_for_loop == 0 ) { return false; } } + sampleFrame * tmp = NULL; @@ -673,10 +692,10 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, SRC_DATA src_data; // Generate output const f_cnt_t margin = 64; - f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) - + margin; - src_data.data_in = getSampleFragment( play_frame, - fragment_size, _looped, &tmp )[0]; + f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) + margin; + src_data.data_in = + getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame )[0]; src_data.data_out = _ab[0]; src_data.input_frames = fragment_size; src_data.output_frames = _frames; @@ -695,10 +714,32 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, src_data.output_frames_gen, _frames ); } // Advance - play_frame += src_data.input_frames_used; - if( _looped ) + switch( _loopmode ) { - play_frame = getLoopedIndex( play_frame ); + case LoopOff: + play_frame += src_data.input_frames_used; + break; + case LoopOn: + play_frame += src_data.input_frames_used; + play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame ); + break; + case LoopPingPong: + { + f_cnt_t left = src_data.input_frames_used; + if( _state->isBackwards() ) + { + play_frame -= src_data.input_frames_used; + if( play_frame < loopStartFrame ) + { + left -= ( loopStartFrame - play_frame ); + play_frame = loopStartFrame; + } + else left = 0; + } + play_frame += left; + play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame ); + break; + } } } else @@ -708,19 +749,43 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, // Generate output memcpy( _ab, - getSampleFragment( play_frame, _frames, _looped, &tmp ), + getSampleFragment( play_frame, _frames, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame ), _frames * BYTES_PER_FRAME ); // Advance - play_frame += _frames; - if( _looped ) + switch( _loopmode ) { - play_frame = getLoopedIndex( play_frame ); + case LoopOff: + play_frame += _frames; + break; + case LoopOn: + play_frame += _frames; + play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame ); + break; + case LoopPingPong: + { + f_cnt_t left = _frames; + if( _state->isBackwards() ) + { + play_frame -= _frames; + if( play_frame < loopStartFrame ) + { + left -= ( loopStartFrame - play_frame ); + play_frame = loopStartFrame; + } + else left = 0; + } + play_frame += left; + play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame ); + break; + } } } delete[] tmp; - _state->m_frameIndex = play_frame; + _state->setBackwards( is_backwards ); + _state->setFrameIndex( play_frame ); return true; @@ -729,45 +794,108 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, -sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _start, - f_cnt_t _frames, bool _looped, sampleFrame * * _tmp ) const +sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, + f_cnt_t _frames, LoopMode _loopmode, sampleFrame * * _tmp, bool * _backwards, + f_cnt_t _loopstart, f_cnt_t _loopend, f_cnt_t _end ) const { - if( _looped ) + + if( _loopmode == LoopOff ) { - if( _start + _frames <= m_loopEndFrame ) + if( _index + _frames <= _end ) { - return m_data + _start; + return m_data + _index; + } + } + else if( _loopmode == LoopOn ) + { + if( _index + _frames <= _loopend ) + { + return m_data + _index; } } else { - if( _start + _frames <= m_endFrame ) - { - return m_data + _start; - } + if( ! *_backwards && _index + _frames < _loopend ) + return m_data + _index; } *_tmp = new sampleFrame[_frames]; - if( _looped ) + if( _loopmode == LoopOff ) { - f_cnt_t copied = m_loopEndFrame - _start; - memcpy( *_tmp, m_data + _start, copied * BYTES_PER_FRAME ); - f_cnt_t loop_frames = m_loopEndFrame - m_loopStartFrame; - while( _frames - copied > 0 ) + f_cnt_t available = _end - _index; + memcpy( *_tmp, m_data + _index, available * BYTES_PER_FRAME ); + memset( *_tmp + available, 0, ( _frames - available ) * + BYTES_PER_FRAME ); + } + else if( _loopmode == LoopOn ) + { + f_cnt_t copied = qMin( _frames, _loopend - _index ); + memcpy( *_tmp, m_data + _index, copied * BYTES_PER_FRAME ); + f_cnt_t loop_frames = _loopend - _loopstart; + while( copied < _frames ) { f_cnt_t todo = qMin( _frames - copied, loop_frames ); - memcpy( *_tmp + copied, m_data + m_loopStartFrame, - todo * BYTES_PER_FRAME ); + memcpy( *_tmp + copied, m_data + _loopstart, todo * BYTES_PER_FRAME ); copied += todo; } } else { - f_cnt_t available = m_endFrame - _start; - memcpy( *_tmp, m_data + _start, available * BYTES_PER_FRAME ); - memset( *_tmp + available, 0, ( _frames - available ) * - BYTES_PER_FRAME ); + f_cnt_t pos = _index; + bool backwards = *_backwards; + f_cnt_t copied = 0; + + if( backwards ) + { + copied = qMin( _frames, pos - _loopstart ); + for( int i=0; i < copied; i++ ) + { + //memcpy( *_tmp + i, m_data + pos - i, BYTES_PER_FRAME ); + (*_tmp)[i][0] = m_data[ pos - i ][0]; + (*_tmp)[i][1] = m_data[ pos - i ][1]; + } + pos -= copied; + if( pos == _loopstart ) backwards = false; + } + else + { + copied = qMin( _frames, _loopend - pos ); + memcpy( *_tmp, m_data + pos, copied * BYTES_PER_FRAME ); + pos += copied; + if( pos == _loopend ) backwards = true; + } + + while( copied < _frames ) + { + if( pos >= _loopend ) backwards = true; + if( pos <= _loopstart ) backwards = false; + pos = qBound( _loopstart, pos, _loopend ); + /*qDebug( backwards ? "backwards" : "forwards" ); + qDebug( "pos %d", pos );*/ + if( backwards ) + { + f_cnt_t todo = qMin( _frames - copied, pos - _loopstart ); + for ( int i=0; i < todo; i++ ) + { + //memcpy( *_tmp + ( copied + i ), m_data + ( pos - i ), BYTES_PER_FRAME ); + (*_tmp)[ copied + i ][0] = m_data[ pos - i ][0]; + (*_tmp)[ copied + i ][1] = m_data[ pos - i ][1]; + } + pos -= todo; + copied += todo; + if( pos == _loopstart ) backwards = false; + } + else + { + f_cnt_t todo = qMin( _frames - copied, _loopend - pos ); + memcpy( *_tmp + copied, m_data + pos, todo * BYTES_PER_FRAME ); + pos += todo; + copied += todo; + if( pos == _loopend ) backwards = true; + } + } + *_backwards = backwards; } return *_tmp; @@ -776,17 +904,30 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _start, -f_cnt_t SampleBuffer::getLoopedIndex( f_cnt_t _index ) const +f_cnt_t SampleBuffer::getLoopedIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const { - if( _index < m_loopEndFrame ) + if( _index < _endf ) { return _index; } - return m_loopStartFrame + ( _index - m_loopStartFrame ) - % ( m_loopEndFrame - m_loopStartFrame ); + return _startf + ( _index - _startf ) + % ( _endf - _startf ); } +f_cnt_t SampleBuffer::getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const +{ + if( _index < _endf ) + { + return _index; + } + const f_cnt_t looplen = _endf - _startf; + const f_cnt_t looppos = ( _index - _endf ) % ( looplen*2 ); + + return ( looppos < looplen ) + ? _endf - looppos + : _startf + ( looppos - looplen ); +} void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, @@ -915,19 +1056,19 @@ QString SampleBuffer::openAndSetWaveformFile() { m_audioFile = configManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; } - + QString fileName = this->openAudioFile(); if(!fileName.isEmpty()) { this->setAudioFile( fileName ); - } - else + } + else { m_audioFile = ""; } - return fileName; + return fileName; } @@ -1328,7 +1469,8 @@ QString SampleBuffer::tryToMakeAbsolute( const QString & _file ) SampleBuffer::handleState::handleState( bool _varying_pitch ) : m_frameIndex( 0 ), - m_varyingPitch( _varying_pitch ) + m_varyingPitch( _varying_pitch ), + m_isBackwards( false ) { int error; if( ( m_resamplingData = src_new(/*